/**
  * LzMakeLoadSprite.as
  *
  * @copyright Copyright 2001-2008 Laszlo Systems, Inc.  All Rights Reserved.
  *            Use is subject to license terms.
  *
  * @topic Kernel
  * @subtopic AS2
  */

/**
  * @access private
  */
function LzMakeLoadSprite(  ){

}

LzMakeLoadSprite.transformerID = 0;

/**
  * Makes the given view able to load a remote resource
  */
LzMakeLoadSprite.transform = function ( v , src , cache, headers, filetype ){
    //super.transform( v );

    for ( var k in this){
        if (typeof v[k] == 'function' && this[k]) {
            // replace old method with _methodname to support super
            //Debug.write('replacing _' + k + ' with ' + k + ' in ' + v);
            v[ '___' + k ] = v[k];
            v[k] = this[k];
        } else {
            //Debug.write('adding ' + k + ' to ' + v);
            v[k] = this[k];
        }
    }

//     Debug.trace(v, 'setHeight');
//     Debug.trace(v, 'setWidth');
//     Debug.monitor(v, 'resourcewidth')
//     Debug.monitor(v, 'resourceheight')
//     Debug.monitor(v, '_xscale')
//     Debug.monitor(v, '_yscale')

    v.firstsrc = src;
    v.firstcache = cache;
    v.firstheaders = headers;
    v.firstfiletype = filetype;

    if ( v.__LZmovieClipRef == null ){
        v.makeContainerResource();
    }

    if (v.__LZhaser ){
        v.createLoader();
    } else {
        //the view doesn't have the empty resource. We need to try and replace
        //it
        v.makeContainerResource();
        if (! this.loader) v.createLoader();
    }

//     var mc = v.__LZmovieClipRef;
//     Debug.debug(mc, '_width', mc._width);
//     Debug.debug(mc, '_height', mc._height);
//     Debug.debug(mc, '_xscale', mc._xscale);
//     Debug.debug(mc, '_yscale', mc._yscale);
}

/**
  * @access private
  */
LzMakeLoadSprite.doReplaceResource = function (){
    //this gets called when a view applies the load transformer but it
    //already had a resource

    // use shadowed doReplaceResource()
    this.___doReplaceResource( );
    if (this._newrescname.indexOf('http:') == 0 || this._newrescname.indexOf('https:') == 0){
        this.createLoader();
    }
}

/**
  * @access private
  */
LzMakeLoadSprite.createLoader = function (){
    this.loader = new LzMediaLoader( this , {} );
    this.updateDel = new LzDelegate( this , "updateAfterLoad" );
    this.updateDel.register( this.loader , "onloaddone" );

    this.errorDel = new LzDelegate( this , "__LZsendError" );
    this.errorDel.register( this.loader , "onerror" );

    this.timeoutDel = new LzDelegate( this , "__LZsendTimeout" );
    this.timeoutDel.register( this.loader , "ontimeout" );

    //Debug.write('LzMakeLoadSprite.createLoader', this.firstsrc)
    if ( this.firstsrc != null ){
        this.setSource( this.firstsrc , this.firstcache, this.firstheaders, this.firstfiletype );
    }
}

/**
  * @access private
  * @param String newSrc: The url from which to load the resource for this view.
  * @param String cache: If set, controls caching behavior. Choices are
  * "none" , "clientonly" , "serveronly" , "both" -- where both is the default.
  * @param String headers: Headers to send with the request (if any.)
  * @param String filetype: Filetype, e.g. 'mp3' or 'jpg'.  If not specified, it will be derived from the URL.
  */
LzMakeLoadSprite.setSource = function ( newSrc , cache , headers , filetype ){
    if (this.loader.mc.loading == true) {
        LzLoadQueue.unloadRequest(this.loader.mc);
    }
    //Debug.write('setSource', newSrc);
    if (newSrc == '' || newSrc == ' ' || newSrc == null) {
        Debug.error('setSource called with an empty url');
        return;
    }

    this.stopTrackPlay();
    this.isloaded = false;
    if ( this.queuedplayaction == null ){
        this.queuePlayAction( "checkPlayStatus" );
    }

    this.resource = newSrc;
    //this.owner.resource = newSrc;
    //if (this.owner.onresource) this.owner.onresource.sendEvent( newSrc );

    this.owner.__LZvizLoad = false;
    this.owner.__LZupdateShown();
    this.loader.request( newSrc , cache , headers, filetype );
}

/**
  * This method keeps the behavior of setResource consistent between
  * load-transformed views and those that aren't
  * @access private
  */
LzMakeLoadSprite.setResource = function ( nresc ){
    // unload anything currently loading...
    if (this.loader.mc.loading == true) {
        LzLoadQueue.unloadRequest(this.loader.mc);
    }
    if ( nresc == "empty" ){
        // call shadowed setResource()
        this.___setResource( nresc );
    } else {
        if ( nresc.indexOf('http:') == 0 || nresc.indexOf('https:') == 0){
            this.setSource( nresc );
            return;
        }
        this.loader.attachLoadMovie( nresc );
        if ( this.queuedplayaction == null ){
            this.queuePlayAction( "checkPlayStatus" );
        }
        //this.updateAfterLoad();
    }

//     var mc = this.__LZmovieClipRef;
//     Debug.debug(mc, '_width', mc._width);
//     Debug.debug(mc, '_height', mc._height);
//     Debug.debug(mc, '_xscale', mc._xscale);
//     Debug.debug(mc, '_yscale', mc._yscale);
}

//handle error

/**
  * Updates movieclip properties after the resource has loaded
  * @access private
  */
LzMakeLoadSprite.updateAfterLoad = function (ignore){

    this.isloaded = true;
    var mc = this.getMCRef();
    this.resourcewidth = mc._width;
    this.resourceheight = mc._height;

    this.currentframe = mc._currentframe;

    var tfchg = this.totalframes != mc._totalframes;
    if ( tfchg ){
        this.totalframes = mc._totalframes;
        this.owner.resourceevent('totalframes', this.totalframes);
    }

    if ( this.totalframes > 1 ){
        this.checkPlayStatus();
    }

    this.setHeight(this.hassetheight?this.height:null);
    this.setWidth(this.hassetwidth?this.width:null);

    if (this.__contextmenu) {
        this.setContextMenu(this.__contextmenu);
    }

    this.owner.__LZvizLoad = true;
    this.owner.__LZupdateShown();
    this.owner.resourceload({width: this.resourcewidth, height: this.resourceheight, resource: this.resource, skiponload: false});
    this.owner.reevaluateSize( );
    //if (this.owner.onload) this.owner.onload.sendEvent( this.loader.mc );

    //Debug.write('Sprite.updateAfterLoad', mc, this.resourcewidth, this.resourceheight, this.resource, this.owner.resource, this.owner, this.x);
    //Debug.warn('Sprite.updateAfterLoad');
}

/**
  * Unloads the media
  * @access private
  */
LzMakeLoadSprite.unload = function () {
    this.loader.unload( this.loader.mc );
}

/**
  * Get a reference to the control mc
  * @access private
  */
LzMakeLoadSprite.getMCRef = function () {
    //return null if not loaded
    if (this.loader.isaudio) return this.loader.mc;
    return this.isloaded ? this.loader.getLoadMC() : null;
}

/**
  * Get the number of bytes loaded so far
  * @return: A number that is the maximum offset for this resource
  */
LzMakeLoadSprite.getLoadBytes = function (){
    return this.getMCRef().getBytesLoaded();
}

/**
  * Get the total number of bytes for the attached resource
  * @return: A number that is the maximum offset for this resource
  */
LzMakeLoadSprite.getMaxBytes = function (){
    return this.getMCRef().getBytesTotal();
}

/**
  * @access private
  */
LzMakeLoadSprite.__LZsendError = function ( e ){
    this.resourcewidth = 0;
    this.resourceheight = 0;
    if (this.owner) this.owner.resourceloaderror( e )
}

/**
  * @access private
  */
LzMakeLoadSprite.__LZsendTimeout = function ( e ){
    this.resourcewidth = 0;
    this.resourceheight = 0;
    if (this.owner) this.owner.resourceloadtimeout( e )
}

/**
  * @access private
  */
LzMakeLoadSprite.destroy = function () {
    if ('updateDel' in this)
         this.updateDel.unregisterAll();
    if ('errorDel' in this)
         this.errorDel.unregisterAll();
    if ('timeoutDel' in this)
         this.timeoutDel.unregisterAll();

    this.loader.unload( this.loader.mc );

    // call shadowed destroy()
    this.___destroy();
}

/**
  * LzMakeLoadSprite.as
  *
  * @copyright Copyright 2001-2008 Laszlo Systems, Inc.  All Rights Reserved.
  *            Use is subject to license terms.
  *
  * @topic Kernel
  * @subtopic AS2
  */

/**
  * @access private
  */
var LzMakeLoadSprite = {};

/**
  * Makes the given view able to load a remote resource
  */
LzMakeLoadSprite.transform = function (v, src, cache, headers, filetype) {
    for (var k in this) {
        if (k != "transform") {
            if (typeof v[k] == 'function') {
                // replace old method with _methodname to support super
                v[ '___' + k ] = v[k];
            }
            v[k] = this[k];
        }
    }

    if (v.__LZmovieClipRef == null || ! v.__LZhaser) {
        //the view doesn't have the empty resource. We need to try and replace it
        v.makeContainerResource();
    }

    v.createLoader(src, cache, headers, filetype);
}

/**
  * @access private
  */
LzMakeLoadSprite.createLoader = function (src, cache, headers, filetype) {
    this.loader = new LzMediaLoader(this, {});

    this.updateDel = new LzDelegate(this, "updateAfterLoad", this.loader, "onloaddone");
    this.errorDel = new LzDelegate(this, "__LZsendError", this.loader, "onerror");
    this.timeoutDel = new LzDelegate(this, "__LZsendTimeout", this.loader, "ontimeout");

    if (src != null) {
        this.setSource(src, cache, headers, filetype);
    }
}

/**
  * @access private
  * @param String src: The url from which to load the resource for this view.
  * @param String cache: If set, controls caching behavior. Choices are
  * "none" , "clientonly" , "serveronly" , "both" -- where both is the default.
  * @param String headers: Headers to send with the request (if any.)
  * @param String filetype: Filetype, e.g. 'mp3' or 'jpg'.  If not specified, it will be derived from the URL.
  */
LzMakeLoadSprite.setSource = function (src, cache, header, filetype) {
    if (this.loader.mc.loading == true) {
        LzLoadQueue.unloadRequest(this.loader.mc);
    }

    if (src == '' || src == ' ' || src == null) {
        if ($debug) Debug.error('setSource called with an empty url');
        return;
    }

    this.stopTrackPlay();
    this.isloaded = false;
    if (this.queuedplayaction == null) {
        this.queuePlayAction("checkPlayStatus");
    }

    this.resource = src;
    //this.owner.resource = src;
    //if (this.owner.onresource) this.owner.onresource.sendEvent( src );

    this.owner.__LZvizLoad = false;
    this.owner.__LZupdateShown();
    this.loader.request(src, cache, headers, filetype);
}

/**
  * This method keeps the behavior of setResource consistent between
  * load-transformed views and those that aren't
  * @access private
  */
LzMakeLoadSprite.setResource = function (nresc) {
    // unload anything currently loading...
    if (this.loader.mc.loading == true) {
        LzLoadQueue.unloadRequest(this.loader.mc);
    }
    if (nresc == "empty") {
        // call shadowed setResource()
        this.___setResource(nresc);
    } else if (nresc.indexOf('http:') == 0 || nresc.indexOf('https:') == 0) {
        this.setSource(nresc);
    } else {
        this.loader.attachLoadMovie(nresc);
        if (this.queuedplayaction == null) {
            this.queuePlayAction("checkPlayStatus");
        }
        //this.updateAfterLoad();
    }
}

/**
  * Updates movieclip properties after the resource has loaded
  * @access private
  */
LzMakeLoadSprite.updateAfterLoad = function (ignore) {
    this.isloaded = true;

    var mc = this.getMCRef();
    this.resourcewidth = mc._width;
    this.resourceheight = mc._height;
    this.currentframe = mc._currentframe;

    var tfchg = this.totalframes != mc._totalframes;
    if (tfchg) {
        this.totalframes = mc._totalframes;
        this.owner.resourceevent('totalframes', this.totalframes);
    }

    if (this.totalframes > 1) {
        this.checkPlayStatus();
    }

    this.setHeight(this.hassetheight ? this.height : null);
    this.setWidth(this.hassetwidth ? this.width : null);

    if (this.__contextmenu) {
        this.setContextMenu(this.__contextmenu);
    }

    this.owner.__LZvizLoad = true;
    this.owner.__LZupdateShown();
    this.owner.resourceload({width: this.resourcewidth, height: this.resourceheight, resource: this.resource, skiponload: false});
    this.owner.reevaluateSize();

    //if (this.owner.onload) this.owner.onload.sendEvent( this.loader.mc );
}

/**
  * Unloads the media
  * @access private
  */
LzMakeLoadSprite.unload = function () {
    this.loader.unload(this.loader.mc);
}

/**
  * Get a reference to the control mc
  * @access private
  */
LzMakeLoadSprite.getMCRef = function () {
    //return null if not loaded
    if (this.loader.isaudio) return this.loader.mc;
    return this.isloaded ? this.loader.getLoadMC() : null;
}

/**
  * Get the number of bytes loaded so far
  * @return: A number that is the maximum offset for this resource
  */
LzMakeLoadSprite.getLoadBytes = function () {
    return this.getMCRef().getBytesLoaded();
}

/**
  * Get the total number of bytes for the attached resource
  * @return: A number that is the maximum offset for this resource
  */
LzMakeLoadSprite.getMaxBytes = function () {
    return this.getMCRef().getBytesTotal();
}

/**
  * @access private
  */
LzMakeLoadSprite.__LZsendError = function (e) {
    this.resourcewidth = 0;
    this.resourceheight = 0;
    if (this.owner) this.owner.resourceloaderror(e)
}

/**
  * @access private
  */
LzMakeLoadSprite.__LZsendTimeout = function (e) {
    this.resourcewidth = 0;
    this.resourceheight = 0;
    if (this.owner) this.owner.resourceloadtimeout(e)
}

/**
  * @access private
  */
LzMakeLoadSprite.destroy = function () {
    if ('updateDel' in this)
         this.updateDel.unregisterAll();
    if ('errorDel' in this)
         this.errorDel.unregisterAll();
    if ('timeoutDel' in this)
         this.timeoutDel.unregisterAll();

    this.loader.unload(this.loader.mc);

    // call shadowed destroy()
    this.___destroy();
}

Change 20080902-bargull-2wD by bargull@dell--p4--2-53 on 2008-09-02 21:31:23
    in /home/Admin/src/svn/openlaszlo/trunk
    for http://svn.openlaszlo.org/openlaszlo/trunk

Summary: LzMakeLoadSprite cleanup

New Features:

Bugs Fixed: LPP-5551 (part 1 - cleanup)

Technical Reviewer: max
QA Reviewer: (pending)
Doc Reviewer: (pending)

Documentation:

Release Notes:

Details:
As part of LPP-5551, I like to do some code cleanup.
LzMakeLoadSprite:
- consistent code-formatting
- changed LzMakeLoadSprite from function to object
- removed out commented debug-code
- removed first* properties, instead call "createLoader" directly
- removed override of "doReplaceResource" because it was superfluous as "_newrescname" was never defined
- removed "if (! this.loader)"-check in LzMakeLoadSprite.transform, because it always yield true as there is no "loader"-field defined in LzMakeLoadSprite
- removed "transformerID", no where referenced in the lfc

Tests:

git-svn-id: d62bde4b5aa582fdf07c8d403e53e0399a7ed285@10856 fa20e4f9-1d0a-0410-b5f3-dc9c16b8b17c


package aerys.minko.type
{
	public final class Signal
	{
		private var _name					: String;
        private var _enabled        		: Boolean;
		private var _disableWhenNoCallbacks	: Boolean;

		private var _callbacks				: Vector.<Function>;
		private var _numCallbacks			: uint;

		private var _executed				: Boolean;
		private var _numAdded				: uint;
		private var _toAdd					: Vector.<Function>;
		private var _numRemoved				: uint;
		private var _toRemove				: Vector.<Function>;

        public function get enabled() : Boolean
        {
            return _enabled;
        }
        public function set enabled(value : Boolean) : void
        {
            _enabled = value;
        }

		public function get numCallbacks() : uint
		{
			return _numCallbacks;
		}

		public function Signal(name 					: String,
							   enabled 					: Boolean 	= true,
							   disableWhenNoCallbacks	: Boolean	= false)
		{
			_name = name;
            _enabled = enabled;
			_disableWhenNoCallbacks = disableWhenNoCallbacks;
		}

		public function add(callback : Function) : void
		{
            if (_callbacks && _callbacks.indexOf(callback) >= 0)
            {
                var removeIndex : int = _toRemove ? _toRemove.indexOf(callback) : -1;

                // if that callback is in the temp. remove list, we simply remove it from this list
                // instead of removing/adding it all over again
                if (removeIndex >= 0)
                {
                    --_numRemoved;
                    _toRemove[removeIndex] = _toRemove[_numRemoved];
                    _toRemove.length = _numRemoved;

                    return;
                }
                else
                    throw new Error('The same callback cannot be added twice.');
            }

			if (_executed)
			{
				if (_toAdd)
					_toAdd.push(callback);
				else
					_toAdd = new <Function>[callback];
				++_numAdded;

				return ;
			}

			if (_callbacks) _callbacks[_numCallbacks] = callback;
			else _callbacks = new <Function>[callback];
			++_numCallbacks;

			if (_numCallbacks == 1 && _disableWhenNoCallbacks)
				_enabled = true;
		}

		public function remove(callback : Function) : void
		{
			var index : int = (_callbacks ? _callbacks.indexOf(callback) : -1);

			if (index < 0)
            {
                var addIndex : int = _toAdd ? _toAdd.indexOf(callback) : -1;

                // if that callback is in the temp. add list, we simply remove it from this list
                // instead of adding/removing it all over again
                if (addIndex >= 0)
                {
                    --_numAdded;
                    _toAdd[addIndex] = _toAdd[_numAdded];
                    _toAdd.length = _numAdded;
                }
                else
    				throw new Error('This callback does not exist.');
            }

			if (_executed)
			{
				if (_toRemove)
					_toRemove.push(callback);
				else
					_toRemove = new <Function>[callback];
				++_numRemoved;

				return ;
			}

			--_numCallbacks;
			_callbacks[index] = _callbacks[_numCallbacks];
			_callbacks.length = _numCallbacks;

			if (!_numCallbacks && _disableWhenNoCallbacks)
				_enabled = false;
		}

		public function hasCallback(callback : Function) : Boolean
		{
			return _callbacks && _callbacks.indexOf(callback) >= 0;
		}

		public function execute(...params) : void
		{
			if (_numCallbacks && _enabled)
			{
				_executed = true;
				for (var i : uint = 0; i < _numCallbacks; ++i)
					_callbacks[i].apply(null, params);
				_executed = false;

				for (i = 0; i < _numAdded; ++i)
					add(_toAdd[i]);
				_numAdded = 0;
				_toAdd = null;

				for (i = 0; i < _numRemoved; ++i)
					remove(_toRemove[i]);
				_numRemoved = 0;
				_toRemove = null;
			}
		}
	}
}

Update Signal.as

Update Signal.as


/* ***** BEGIN LICENSE BLOCK *****
 *
 * This file is part of Weave.
 *
 * The Initial Developer of Weave is the Institute for Visualization
 * and Perception Research at the University of Massachusetts Lowell.
 * Portions created by the Initial Developer are Copyright (C) 2008-2015
 * the Initial Developer. All Rights Reserved.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * ***** END LICENSE BLOCK ***** */

package weavejs.data
{
	import weavejs.WeaveAPI;
	import weavejs.api.core.ILinkableObject;
	import weavejs.api.data.ICSVParser;
	import weavejs.util.JS;

	/**
	 * Parses and generates CSV-encoded data.
	 */
	public class CSVParser implements ICSVParser, ILinkableObject
	{
		private static const CR:String = '\r';
		private static const LF:String = '\n';
		private static const CRLF:String = '\r\n';

		private static const flascc:Object = null;

		/**
		 * Creates a CSVParser.
		 * @param asyncMode If this is set to true, parseCSV() will work asynchronously
		 *                  and trigger callbacks when it finishes.
		 *                  If this is set to false, no callback collection will be generated
		 *                  for this instance of CSVParser as a result of calling its methods.
		 *                  Note that if asyncMode is enabled, you can only parse one CSV string at a time.
		 * @param delimiter A single character for the delimiter
		 * @param quote A single character for the quote
		 */
		public function CSVParser(asyncMode:Boolean = false, delimiter:String = ',', quote:String = '"')
		{
			this.asyncMode = asyncMode;
			if (delimiter && delimiter.length == 1)
				this.delimiter = delimiter;
			if (quote && quote.length == 1)
				this.quote = quote;
		}

		// modes set in constructor
		private var asyncMode:Boolean;
		private var delimiter:String = ',';
		private var quote:String = '"';
		private var removeBlankLines:Boolean = true;
		private var parseTokens:Boolean = true;

		// async state
		private var csvData:String;
		private var csvDataArray:Array;

		/**
		 * @return The resulting two-dimensional Array from the last call to parseCSV().
		 */
		public function get parseResult():Array
		{
			return csvDataArray;
		}

		/**
		 * @inheritDoc
		 */
		public function parseCSV(csvData:String):Array
		{
			if (asyncMode)
			{
				this.csvData = csvData;
				this.csvDataArray = [];
				// high priority because preparing data is often the first thing we need to do
				WeaveAPI.Scheduler.startTask(this, parseIterate, WeaveAPI.TASK_PRIORITY_HIGH, parseDone);
			}
			else
			{
				csvDataArray = flascc.parseCSV(csvData, delimiter, quote, removeBlankLines, parseTokens);
			}

			return csvDataArray;
		}

		private function parseIterate(stopTime:int):Number
		{
			// This isn't actually asynchronous at the moment, but moving the code to
			// FlasCC made it so much faster that it won't matter most of the time.
			// The async code structure is kept in case we want to make it asynchronous again in the future.
			flascc.parseCSV(csvData, delimiter, quote, removeBlankLines, parseTokens, csvDataArray);
			csvData = null;
			return 1;
		}

		private function parseDone():void
		{
			if (asyncMode)
				Weave.getCallbacks(this).triggerCallbacks();
		}

		/**
		 * @inheritDoc
		 */
		public function parseCSVRow(csvData:String):Array
		{
			if (csvData == null)
				return null;

			var rows:Array = flascc.parseCSV(csvData, delimiter, quote, removeBlankLines, parseTokens);
			if (rows.length == 0)
				return rows;
			if (rows.length == 1)
				return rows[0];
			// flatten
			return [].concat.apply(null, rows);
		}

		/**
		 * @inheritDoc
		 */
		public function createCSV(rows:Array):String
		{
			return flascc.createCSV(rows, delimiter, quote);
		}

		/**
		 * @inheritDoc
		 */
		public function createCSVRow(row:Array):String
		{
			return row ? flascc.createCSV([row], delimiter, quote) : null;
		}

		/**
		 * @inheritDoc
		 */
		public function convertRowsToRecords(rows:Array, headerDepth:int = 1):Array
		{
			if (rows.length < headerDepth)
				throw new Error("headerDepth is greater than the number of rows");
			assertHeaderDepth(headerDepth);

			var records:Array = new Array(rows.length - headerDepth);
			for (var r:int = headerDepth; r < rows.length; r++)
			{
				var record:Object = {};
				var row:Array = rows[r];
				for (var c:int = 0; c < row.length; c++)
				{
					var output:Object = record;
					var cell:Object = row[c];
					for (var h:int = 0; h < headerDepth; h++)
					{
						var colName:String = rows[h][c];
						if (h < headerDepth - 1)
						{
							if (!output[colName])
								output[colName] = {};
							output = output[colName];
						}
						else
							output[colName] = cell;
					}
				}
				records[r - headerDepth] = record;
			}
			return records;
		}

		/**
		 * @inheritDoc
		 */
		public function getRecordFieldNames(records:Array, includeNullFields:Boolean = false, headerDepth:int = 1):Array
		{
			assertHeaderDepth(headerDepth);

			var nestedFieldNames:Object = {};
			for each (var record:Object in records)
				_outputNestedFieldNames(record, includeNullFields, nestedFieldNames, headerDepth);

			var fields:Array = [];
			_collapseNestedFieldNames(nestedFieldNames, fields);
			return fields.sort();
		}
		private function _outputNestedFieldNames(record:Object, includeNullFields:Boolean, output:Object, depth:int):void
		{
			for (var field:String in record)
			{
				if (includeNullFields || record[field] != null)
				{
					if (depth == 1)
					{
						output[field] = false;
					}
					else
					{
						if (!output[field])
							output[field] = {};
						_outputNestedFieldNames(record[field], includeNullFields, output[field], depth - 1);
					}
				}
			}
		}
		private function _collapseNestedFieldNames(nestedFieldNames:Object, output:Array, prefix:Array = null):void
		{
			for (var field:String in nestedFieldNames)
			{
				if (nestedFieldNames[field]) // either an Object or false
				{
					_collapseNestedFieldNames(nestedFieldNames[field], output, prefix ? prefix.concat(field) : [field]);
				}
				else // false means reached full nesting depth
				{
					if (prefix) // is depth > 1?
						output.push(prefix.concat(field)); // output the list of nested field names
					else
						output.push(field); // no array when max depth is 1
				}
			}
		}

		/**
		 * @inheritDoc
		 */
		public function convertRecordsToRows(records:Array, columnOrder:Array = null, allowBlankColumns:Boolean = false, headerDepth:int = 1):Array
		{
			assertHeaderDepth(headerDepth);

			var fields:Array = columnOrder;
			if (fields == null)
				fields = getRecordFieldNames(records, allowBlankColumns, headerDepth);

			var r:int;
			var c:int;
			var cell:Object;
			var row:Array;
			var rows:Array = new Array(records.length + headerDepth);

			// construct multiple header rows from field name chains
			for (r = 0; r < headerDepth; r++)
			{
				row = new Array(fields.length);
				for (c = 0; c < fields.length; c++)
				{
					if (headerDepth > 1)
						row[c] = fields[c][r] || ''; // fields are Arrays
					else
						row[c] = fields[c] || ''; // fields are Strings
				}
				rows[r] = row;
			}

			for (r = 0; r < records.length; r++)
			{
				var record:Object = records[r];
				row = new Array(fields.length);
				for (c = 0; c < fields.length; c++)
				{
					if (headerDepth == 1)
					{
						// fields is an Array of Strings
						cell = record[fields[c]];
					}
					else
					{
						// fields is an Array of Arrays
						cell = record;
						for each (var field:String in fields[c])
							if (cell)
								cell = cell[field];
					}
					row[c] = cell != null ? String(cell) : '';
				}
				rows[headerDepth + r] = row;
			}
			return rows;
		}

		private static function assertHeaderDepth(headerDepth:int):void
		{
			if (headerDepth < 1)
				throw new Error("headerDepth must be > 0");
		}

		//test();
		private static var _tested:Boolean = false;
		private static function test():void
		{
			if (_tested)
				return;
			_tested = true;

			var _:Object = {};
			_.parser = WeaveAPI.CSVParser;
			_.csv=[
				'internal,internal,public,public,public,private,private,test',
				'id,type,title,keyType,dataType,connection,sqlQuery,empty',
				'2,1,state name,fips,string,resd,"select fips,name from myschema.state_data",',
				'3,1,population,fips,number,resd,"select fips,pop from myschema.state_data",',
				'1,0,state data table'
			].join('\n');
			_.table = _.parser.parseCSV(_.csv);
			_.records = _.parser.convertRowsToRecords(_.table, 2);
			_.rows = _.parser.convertRecordsToRows(_.records, null, false, 2);
			_.fields = _.parser.getRecordFieldNames(_.records, false, 2);
			_.fieldOrder = _.parser.parseCSV('internal,id\ninternal,type\npublic,title\npublic,keyType\npublic,dataType\nprivate,connection\nprivate,sqlQuery');
			_.rows2 = _.parser.convertRecordsToRows(_.records, _.fieldOrder, false, 2);
			JS.log(_);
		}
	}
}

/*
	Weave (Web-based Analysis and Visualization Environment)
	Copyright (C) 2008-2011 University of Massachusetts Lowell

	This file is a part of Weave.

	Weave is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License, Version 3,
	as published by the Free Software Foundation.

	Weave is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with Weave.  If not, see <http://www.gnu.org/licenses/>.
*/

package weavejs.data
{
	import weavejs.WeaveAPI;
	import weavejs.api.core.ILinkableObject;
	import weavejs.api.data.ICSVParser;
	import weavejs.util.AsyncSort;
	import weavejs.util.JS;
	import weavejs.util.StandardLib;

	/**
	 * This is an all-static class containing functions to parse and generate valid CSV files.
	 * Ported from AutoIt Script to Flex. Original author: adufilie
	 *
	 * @author skolman
	 * @author adufilie
	 */
	public class CSVParser implements ICSVParser, ILinkableObject
	{
		private static const CR:String = '\r';
		private static const LF:String = '\n';
		private static const CRLF:String = '\r\n';

		/**
		 * @param delimiter
		 * @param quote
		 * @param asyncMode If this is set to true, parseCSV() will work asynchronously and trigger callbacks when it finishes.
		 *                  Note that if asyncMode is enabled, you can only parse one CSV string at a time.
		 */
		public function CSVParser(asyncMode:Boolean = false, delimiter:String = ',', quote:String = '"')
		{
			this.asyncMode = asyncMode;
			if (delimiter && delimiter.length == 1)
				this.delimiter = delimiter;
			if (quote && quote.length == 1)
				this.quote = quote;
		}

		// modes set in constructor
		private var asyncMode:Boolean;
		private var delimiter:String = ',';
		private var quote:String = '"';
		private var parseTokens:Boolean = true;

		// async state
		private var csvData:String;
		private var csvDataArray:Array;
		private var i:int;
		private var row:int;
		private var col:int;
		private var escaped:Boolean;

		/**
		 * @return  The resulting two-dimensional Array from the last call to parseCSV().
		 */
		public function get parseResult():Array
		{
			return csvDataArray;
		}

		/**
		 * @inheritDoc
		 */
		public function parseCSV(csvData:String):Array
		{
			// initialization
			this.csvData = csvData;
			this.csvDataArray = [];
			this.i = 0;
			this.row = 0;
			this.col = 0;
			this.escaped = false;

			if (asyncMode)
			{
				// high priority because preparing data is often the first thing we need to do
				WeaveAPI.Scheduler.startTask(this, parseIterate, WeaveAPI.TASK_PRIORITY_HIGH, parseDone);
			}
			else
			{
				parseIterate(Number.MAX_VALUE);
				parseDone();
			}

			return csvDataArray;
		}

		private function parseIterate(stopTime:int):Number
		{
			// run initialization code on first iteration
			if (i == 0)
			{
				if (!csvData) // null or empty string?
					return 1; // done

				// start off first row with an empty string token
				csvDataArray[row] = [''];
			}

			while (JS.now() < stopTime)
			{
				if (i >= csvData.length)
					return 1; // done

				var currentChar:String = csvData.charAt(i);
				var twoChar:String = currentChar + csvData.charAt(i+1);
				if (escaped)
				{
					if (twoChar == quote+quote) //escaped quote
					{
						csvDataArray[row][col] += (parseTokens?currentChar:twoChar);//append quote(s) to current token
						i += 1; //skip second quote mark
					}
					else if (currentChar == quote)	//end of escaped text
					{
						escaped = false;
						if (!parseTokens)
						{
							csvDataArray[row][col] += currentChar;//append quote to current token
						}
					}
					else
					{
						csvDataArray[row][col] += currentChar;//append quotes to current token
					}
				}
				else
				{

					if (twoChar == delimiter+quote)
					{
						escaped = true;
						col += 1;
						csvDataArray[row][col] = (parseTokens?'':quote);
						i += 1; //skip quote mark
					}
					else if (currentChar == quote && csvDataArray[row][col] == '')		//start new token
					{
						escaped = true;
						if (!parseTokens)
							csvDataArray[row][col] += currentChar;
					}
					else if (currentChar == delimiter)		//start new token
					{
						col += 1;
						csvDataArray[row][col] = '';
					}
					else if (twoChar == CRLF)	//then start new row
					{
						i += 1; //skip line feed
						row += 1;
						col = 0;
						csvDataArray[row] = [''];
					}
					else if (currentChar == CR)	//then start new row
					{
						row += 1;
						col = 0;
						csvDataArray[row] = [''];
					}
					else if (currentChar == LF)	//then start new row
					{
						row += 1;
						col = 0;
						csvDataArray[row] = [''];
					}
					else //append single character to current token
						csvDataArray[row][col] += currentChar;
				}
				i++;
			}

			return i / csvData.length;
		}

		private function parseDone():void
		{
			// if there is more than one row and last row is empty,
			// remove last row assuming it is there because of a newline at the end of the file.
			for (var iRow:int = csvDataArray.length; iRow--;)
			{
				var dataLine:Array = csvDataArray[iRow];

				if (dataLine.length == 1 && dataLine[0] == '')
					csvDataArray.splice(iRow, 1);
			}

			if (asyncMode)
				Weave.getCallbacks(this).triggerCallbacks();
		}

		/**
		 * @inheritDoc
		 */
		public function createCSV(rows:Array):String
		{
			var lines:Array = new Array(rows.length);
			for (var i:int = rows.length; i--;)
			{
				var tokens:Array = new Array(rows[i].length);
				for (var j:int = tokens.length; j--;)
					tokens[j] = createCSVToken(rows[i][j]);

				lines[i] = tokens.join(delimiter);
			}
			var csvData:String = lines.join(LF);
			return csvData;
		}

		/**
		 * @inheritDoc
		 */
		public function parseCSVRow(csvData:String):Array
		{
			if (csvData == null)
				return null;

			var rows:Array = parseCSV(csvData);
			if (rows.length == 0)
				return rows;
			if (rows.length == 1)
				return rows[0];
			// flatten
			return [].concat.apply(null, rows);
		}

		/**
		 * @inheritDoc
		 */
		public function createCSVRow(row:Array):String
		{
			return createCSV([row]);
		}

		/**
		 * @inheritDoc
		 */
		public function parseCSVToken(token:String):String
		{
			var parsedToken:String = '';

			var tokenLength:int = token.length;

			if (token.charAt(0) == quote)
			{
				var escaped:Boolean = true;
				for (var i:int = 1; i <= tokenLength; i++)
				{
					var currentChar:String = token.charAt(i);
					var twoChar:String = currentChar + token.charAt(i+1);

					if (twoChar == quote+quote) //append escaped quote
					{
						i += 1;
						parsedToken += quote;
					}
					else if (currentChar == quote && escaped)
					{
						escaped = false;
					}
					else
					{
						parsedToken += currentChar;
					}
				}
			}
			else
			{
				parsedToken = token;
			}
			return parsedToken;
		}

		/**
		 * @inheritDoc
		 */
		public function createCSVToken(str:String):String
		{
			if (str == null)
				str = '';

			// determine if quotes are necessary
			if ( str.length > 0
				&& str.indexOf(quote) < 0
				&& str.indexOf(delimiter) < 0
				&& str.indexOf(LF) < 0
				&& str.indexOf(CR) < 0
				&& str == StandardLib.trim(str) )
			{
				return str;
			}

			var token:String = quote;
			for (var i:int = 0; i <= str.length; i++)
			{
				var currentChar:String = str.charAt(i);
				if (currentChar == quote)
					token += quote + quote;
				else
					token += currentChar;
			}
			return token + quote;
		}

		/**
		 * @inheritDoc
		 */
		public function convertRowsToRecords(rows:Array, headerDepth:int = 1):Array
		{
			if (rows.length < headerDepth)
				throw new Error("headerDepth is greater than the number of rows");
			assertHeaderDepth(headerDepth);

			var records:Array = new Array(rows.length - headerDepth);
			for (var r:int = headerDepth; r < rows.length; r++)
			{
				var record:Object = {};
				var row:Array = rows[r];
				for (var c:int = 0; c < row.length; c++)
				{
					var output:Object = record;
					var cell:Object = row[c];
					for (var h:int = 0; h < headerDepth; h++)
					{
						var colName:String = rows[h][c];
						if (h < headerDepth - 1)
						{
							if (!output[colName])
								output[colName] = {};
							output = output[colName];
						}
						else
							output[colName] = cell;
					}
				}
				records[r - headerDepth] = record;
			}
			return records;
		}

		/**
		 * @inheritDoc
		 */
		public function getRecordFieldNames(records:Array, includeNullFields:Boolean = false, headerDepth:int = 1):Array
		{
			assertHeaderDepth(headerDepth);

			var nestedFieldNames:Object = {};
			for each (var record:Object in records)
			_outputNestedFieldNames(record, includeNullFields, nestedFieldNames, headerDepth);

			var fields:Array = [];
			_collapseNestedFieldNames(nestedFieldNames, fields);
			return fields;
		}
		private function _outputNestedFieldNames(record:Object, includeNullFields:Boolean, output:Object, depth:int):void
		{
			for (var field:String in record)
			{
				if (includeNullFields || record[field] != null)
				{
					if (depth == 1)
					{
						output[field] = false;
					}
					else
					{
						if (!output[field])
							output[field] = {};
						_outputNestedFieldNames(record[field], includeNullFields, output[field], depth - 1);
					}
				}
			}
		}
		private function _collapseNestedFieldNames(nestedFieldNames:Object, output:Array, prefix:Array = null):void
		{
			for (var field:String in nestedFieldNames)
			{
				if (nestedFieldNames[field]) // either an Object or false
				{
					_collapseNestedFieldNames(nestedFieldNames[field], output, prefix ? prefix.concat(field) : [field]);
				}
				else // false means reached full nesting depth
				{
					if (prefix) // is depth > 1?
						output.push(prefix.concat(field)); // output the list of nested field names
					else
						output.push(field); // no array when max depth is 1
				}
			}
		}

		/**
		 * @inheritDoc
		 */
		public function convertRecordsToRows(records:Array, columnOrder:Array = null, allowBlankColumns:Boolean = false, headerDepth:int = 1):Array
		{
			assertHeaderDepth(headerDepth);

			var fields:Array = columnOrder;
			if (fields == null)
			{
				fields = getRecordFieldNames(records, allowBlankColumns, headerDepth);
				AsyncSort.sortImmediately(fields);
			}

			var r:int;
			var c:int;
			var cell:Object;
			var row:Array;
			var rows:Array = new Array(records.length + headerDepth);

			// construct multiple header rows from field name chains
			for (r = 0; r < headerDepth; r++)
			{
				row = new Array(fields.length);
				for (c = 0; c < fields.length; c++)
				{
					if (headerDepth > 1)
						row[c] = fields[c][r] || ''; // fields are Arrays
					else
						row[c] = fields[c] || ''; // fields are Strings
				}
				rows[r] = row;
			}

			for (r = 0; r < records.length; r++)
			{
				var record:Object = records[r];
				row = new Array(fields.length);
				for (c = 0; c < fields.length; c++)
				{
					if (headerDepth == 1)
					{
						// fields is an Array of Strings
						cell = record[fields[c]];
					}
					else
					{
						// fields is an Array of Arrays
						cell = record;
						for each (var field:String in fields[c])
						if (cell)
							cell = cell[field];
					}
					row[c] = cell != null ? String(cell) : '';
				}
				rows[headerDepth + r] = row;
			}
			return rows;
		}

		private static function assertHeaderDepth(headerDepth:int):void
		{
			if (headerDepth < 1)
				throw new Error("headerDepth must be > 0");
		}

		//test();
		private static var _tested:Boolean = false;
		private static function test():void
		{
			if (_tested)
				return;
			_tested = true;

			var _:Object = {};
			_.parser = WeaveAPI.CSVParser;
			_.csv=[
				'internal,internal,public,public,public,private,private,test',
				'id,type,title,keyType,dataType,connection,sqlQuery,empty',
				'2,1,state name,fips,string,resd,'+'"'+'select fips,name from myschema.state_data'+'"'+',',
				'3,1,population,fips,number,resd,'+'"'+'select fips,pop from myschema.state_data'+'"'+',',
				'1,0,state data table'
			].join('\n');
			_.table = _.parser.parseCSV(_.csv);
			_.records = _.parser.convertRowsToRecords(_.table, 2);
			_.rows = _.parser.convertRecordsToRows(_.records, null, false, 2);
			_.fields = _.parser.getRecordFieldNames(_.records, false, 2);
			_.fieldOrder = _.parser.parseCSV('internal,id\ninternal,type\npublic,title\npublic,keyType\npublic,dataType\nprivate,connection\nprivate,sqlQuery');
			_.rows2 = _.parser.convertRecordsToRows(_.records, _.fieldOrder, false, 2);
			JS.log(_);
		}
	}
}

copy old CSVParser from commit 7a0c543a1b4dcc0d3587d14f261b252f20c88af9


#					-*- shell-script -*-
##
## children.as: This file is part of build-aux.
## Copyright (C) Gostai S.A.S., 2006-2008.
##
## This software is provided "as is" without warranty of any kind,
## either expressed or implied, including but not limited to the
## implied warranties of fitness for a particular purpose.
##
## See the LICENSE file for more information.
## For comments, bug reports and feedback: http://www.urbiforge.com
##

m4_defun([URBI_CHILDREN_PREPARE],
[

# run TITLE COMMAND...
# --------------------
# Run the COMMAND... (which may use its stdin) and output detailed
# logs about the execution.
#
# Leave un $run_prefix.{cmd, out, sta, err, val} the command, standard
# output, exit status, standard error, and instrumentation output.
run_counter=0
run_prefix=
run ()
{
  local title=$[1]
  run_prefix=$run_counter-$(echo $[1] |
			     sed -e 's/[[^a-zA-Z0-9][^a-zA-Z0-9]]*/-/g;s/-$//')
  run_counter=$(($run_counter + 1))
  shift

  echo "$[@]"> $run_prefix.cmd

  # Beware of set -e.
  local sta
  if "$[@]" >$run_prefix.out 2>$run_prefix.err; then
    sta=0
  else
    sta=$?
    title="$title FAIL ($sta)"
  fi
  echo $sta >$run_prefix.sta

  rst_subsection "$me: $title"
  rst_run_report "$title" "$run_prefix"

  return $sta
}

## ------------------------------------------------- ##
## PID sets -- Lower layer for children management.  ##
## ------------------------------------------------- ##

# pids_alive PIDS
# ---------------
# Return whether some of the PIDs point to alive processes,
# i.e., return 1 iff there are no children alive.
pids_alive ()
{
  local pid
  for pid
  do
    # Using "ps PID" to test whether a processus is alive is,
    # unfortunately, non portable.  OS X Tiger always return 0, and
    # outputs the ps-banner and the line of the processus (if there is
    # none, it outputs just the banner).  On Cygwin, "ps PID" outputs
    # everything, and "ps -p PID" outputs the banner, and the process
    # line if alive.  In both cases it exits with success.
    #
    # We once used grep to check the result:
    #
    # ps -p $pid | grep ["^[	]*$pid[^0-9]"]
    #
    # Unfortunately sometimes there are flags displayed before the
    # process number.  Since we never saw a "ps -p PID" that does not
    # display the title line, we expect two lines.
    case $(ps -p $pid | wc -l | sed -e '[s/^[	 ]*//]') in
      1) # process is dead.
	 ;;
      2) # Process is live.
	 return 0;;
      *) error SOFTWARE "unexpected ps output:" "$(ps -p $pid)" ;;
    esac
  done
  return 1
}

# pids_kill PIDS
# --------------
# Kill all the PIDS.  This function can be called twice: once
# before cleaning the components, and once when exiting, so it's
# robust to children no longer in the process table.
pids_kill ()
{
  local pid
  for pid
  do
    if pids_alive $pid; then
      local name
      name=$(cat $pid.name)
      echo "Killing $name (kill -ALRM $pid)"
      kill -ALRM $pid 2>&1 || true
    fi
  done
}

# pids_wait TIMEOUT PIDS
# ----------------------
pids_wait ()
{
  local timeout=$[1]
  shift

  if $INSTRUMENT; then
    timeout=$(($timeout * 5))
  fi

  while pids_alive "$[@]"; do
    if test $timeout -le 0; then
      pids_kill "$[@]"
      break
    fi
    sleep 1
    timeout=$(($timeout - 1))
  done
}

## ---------- ##
## Children.  ##
## ---------- ##

# rst_run_report $TITLE $FILE-PREFIX
# ----------------------------------
# Report the input and output for $FILE-PREFIX.
rst_run_report ()
{
  local title=$[1]
  case $title in
    ?*) title="$title ";;
  esac

  rst_pre "${title}Command"   $[2].cmd
  rst_pre "${title}Pid"       $[2].pid
  rst_pre "${title}Status"    $[2].sta
  rst_pre "${title}Input"     $[2].in
  rst_pre "${title}Output"    $[2].out
  rst_pre "${title}Error"     $[2].err
  rst_pre "${title}Valgrind"  $[2].val
}

# children_alive [CHILDREN]
# -------------------------
# Return whether there are still some children running,
# i.e., return 1 iff there are no children alive.
children_alive ()
{
  local pid
  local pids
  pids=$(children_pid "$@")
  for pid in $pids
  do
    # Using "ps PID" to test whether a processus is alive is,
    # unfortunately, non portable.  OS X Tiger always return 0, and
    # outputs the ps-banner and the line of the processus (if there is
    # none, it outputs just the banner).  On Cygwin, "ps PID" outputs
    # everything, and "ps -p PID" outputs the banner, and the process
    # line if alive.  In both cases it exits with success.
    #
    # We once used grep to check the result:
    #
    # ps -p $pid | grep ["^[	]*$pid[^0-9]"]
    #
    # Unfortunately sometimes there are flags displayed before the
    # process number.  Since we never saw a "ps -p PID" that does not
    # display the title line, we expect two lines.
    case $(ps -p $pid | wc -l | sed -e '[s/^[	 ]*//]') in
      1) # process is dead.
	 ;;
      2) # Process is live.
	 return 0;;
      *) error SOFTWARE "unexpected ps output:" "$(ps -p $pid)" ;;
    esac
  done
  return 1
}

# children_clean [CHILDREN]
# --------------------------
# Remove the children files.
children_clean ()
{
  test $[#] -ne 0 ||
    { set x $children; shift; }

  local i
  for i
  do
    rm -f $i.{cmd,pid,sta,in,out,err,val}
  done
}

# children_pid [CHILDREN]
# -----------------------
# Return the PIDs of the CHILDREN (whether alive or not).
children_pid ()
{
  test $[#] -ne 0 ||
    { set x $children; shift; }

  local i
  for i
  do
    test -f $i.pid ||
      error SOFTWARE "children_pid: cannot find $i.pid."
    cat $i.pid
  done
}

# children_register NAME
# ----------------------
# It is very important that we do not leave some random exit status
# from some previous runs.  Standard output etc. are not a problem:
# they *are* created during the run, while *.sta is created *only* if
# it does not already exists (this is because you can call "wait" only
# once per processus, so to enable calling children_harvest multiple
# times, we create *.sta only if it does not exist).
#
# Creates NAME.pid which contains the PID, and PID.name which contains
# the name.
children_register ()
{
  test $[#] -eq 1
  rm -f $[1].sta
  children="$children $[1]"
  echo $! >$[1].pid
  echo $[1] >$!.name
}

# children_report [CHILDREN]
# --------------------------
# Produce an RST report for the CHILDREN.
children_report ()
{
  test $[#] -ne 0 ||
    { set x $children; shift; }

  local i
  for i
  do
    rst_run_report "$i" "$i"
  done
}

# children_kill [CHILDREN]
# ------------------------
# Kill all the children.  This function can be called twice: once
# before cleaning the components, and once when exiting, so it's
# robust to children no longer in the process table.
children_kill ()
{
  local pids
  pids=$(children_pid "$[@]")
  pids_kill "$pids"
}

# children_harvest [CHILDREN]
# ---------------------------
# Report the exit status of the children.  Can be run several times.
# You might want to call it once in the regular control flow to fetch
# some exit status, but also in a trap, if you were interrupted.  So
# it is meant to be callable multiple times, which might be a danger
# wrt *.sta (cf., children_register).
children_harvest ()
{
  # Harvest exit status.
  test $[#] -ne 0 ||
    { set x $children; shift; }

  local i
  for i
  do
    # Don't wait for children we already waited for.
    if ! test -e $i.sta; then
      local pid
      pid=$(children_pid $i)
      # Beware of set -e.
      local sta
      if wait $pid 2>&1; then
	sta=$?
      else
	sta=$?
      fi
      echo "$sta$(ex_to_string $sta)" >$i.sta
    fi
  done
}

# children_status [CHILDREN]
# --------------------------
# Return the exit status of CHILD.  Must be run after harvesting.
# If several CHILD are given, return the highest exit status.
children_status ()
{
  test $[#] -ne 0 ||
    { set x $children; shift; }

  local res=0
  local i
  for i
  do
    # We need to have waited for these children.
    test -f $i.sta ||
      error SOFTWARE "children_status: cannot find $i.sta." \
		     "Was children_harvest called?"         \
		     "Maybe children_cleanup was already called..."
    local sta=$(sed -n '1{s/^\([[0-9][0-9]]*\).*/\1/;p;q;}' <$i.sta)
    if test $res -lt $sta; then
      res=$sta
    fi
  done
  echo $res
}

# children_wait TIMEOUT [CHILDREN]
# --------------------------------
# Wait for the registered children, and passed TIMEOUT, kill the remaining
# ones.  TIMEOUT is increased by 5 if instrumenting.
children_wait ()
{
  local timeout=$[1]
  shift
  local pids
  pids=$(children_pid "$[@]")
  pids_wait $timeout $pids
}

])

#					-*- shell-script -*-
##
## children.as: This file is part of build-aux.
## Copyright (C) Gostai S.A.S., 2006-2008.
##
## This software is provided "as is" without warranty of any kind,
## either expressed or implied, including but not limited to the
## implied warranties of fitness for a particular purpose.
##
## See the LICENSE file for more information.
## For comments, bug reports and feedback: http://www.urbiforge.com
##

m4_defun([URBI_CHILDREN_PREPARE],
[

# run TITLE COMMAND...
# --------------------
# Run the COMMAND... (which may use its stdin) and output detailed
# logs about the execution.
#
# Leave un $run_prefix.{cmd, out, sta, err, val} the command, standard
# output, exit status, standard error, and instrumentation output.
run_counter=0
run_prefix=
run ()
{
  local title=$[1]
  run_prefix=$run_counter-$(echo $[1] |
			     sed -e 's/[[^a-zA-Z0-9][^a-zA-Z0-9]]*/-/g;s/-$//')
  run_counter=$(($run_counter + 1))
  shift

  echo "$[@]"> $run_prefix.cmd

  # Beware of set -e.
  local sta
  if "$[@]" >$run_prefix.out 2>$run_prefix.err; then
    sta=0
  else
    sta=$?
    title="$title FAIL ($sta)"
  fi
  echo $sta >$run_prefix.sta

  rst_subsection "$me: $title"
  rst_run_report "$title" "$run_prefix"

  return $sta
}

## ------------------------------------------------- ##
## PID sets -- Lower layer for children management.  ##
## ------------------------------------------------- ##

# pids_alive PIDS
# ---------------
# Return whether some of the PIDs point to alive processes,
# i.e., return 1 iff there are no children alive.
pids_alive ()
{
  local pid
  for pid
  do
    # Using "ps PID" to test whether a processus is alive is,
    # unfortunately, non portable.  OS X Tiger always return 0, and
    # outputs the ps-banner and the line of the processus (if there is
    # none, it outputs just the banner).  On Cygwin, "ps PID" outputs
    # everything, and "ps -p PID" outputs the banner, and the process
    # line if alive.  In both cases it exits with success.
    #
    # We once used grep to check the result:
    #
    # ps -p $pid | grep ["^[	]*$pid[^0-9]"]
    #
    # Unfortunately sometimes there are flags displayed before the
    # process number.  Since we never saw a "ps -p PID" that does not
    # display the title line, we expect two lines.
    case $(ps -p $pid | wc -l | sed -e '[s/^[	 ]*//]') in
      1) # process is dead.
	 ;;
      2) # Process is live.
	 return 0;;
      *) error SOFTWARE "unexpected ps output:" "$(ps -p $pid)" ;;
    esac
  done
  return 1
}

# pids_kill PIDS
# --------------
# Kill all the PIDS.  This function can be called twice: once
# before cleaning the components, and once when exiting, so it's
# robust to children no longer in the process table.
pids_kill ()
{
  local pid
  for pid
  do
    if pids_alive $pid; then
      local name
      name=$(cat $pid.name)
      echo "Killing $name (kill -ALRM $pid)"
      kill -ALRM $pid 2>&1 || true
    fi
  done
}

# pids_wait TIMEOUT PIDS
# ----------------------
pids_wait ()
{
  local timeout=$[1]
  shift

  if $INSTRUMENT; then
    timeout=$(($timeout * 5))
  fi

  while pids_alive "$[@]"; do
    if test $timeout -le 0; then
      pids_kill "$[@]"
      break
    fi
    sleep 1
    timeout=$(($timeout - 1))
  done
}

## ---------- ##
## Children.  ##
## ---------- ##

# rst_run_report $TITLE $FILE-PREFIX
# ----------------------------------
# Report the input and output for $FILE-PREFIX.
rst_run_report ()
{
  local title=$[1]
  case $title in
    ?*) title="$title ";;
  esac

  rst_pre "${title}Command"   $[2].cmd
  rst_pre "${title}Pid"       $[2].pid
  rst_pre "${title}Status"    $[2].sta
  rst_pre "${title}Input"     $[2].in
  rst_pre "${title}Output"    $[2].out
  rst_pre "${title}Error"     $[2].err
  rst_pre "${title}Valgrind"  $[2].val
}

# children_alive [CHILDREN]
# -------------------------
# Return whether there are still some children running,
# i.e., return 1 iff there are no children alive.
children_alive ()
{
  local pid
  local pids
  pids=$(children_pid "$[@]")
  for pid in $pids
  do
    # Using "ps PID" to test whether a processus is alive is,
    # unfortunately, non portable.  OS X Tiger always return 0, and
    # outputs the ps-banner and the line of the processus (if there is
    # none, it outputs just the banner).  On Cygwin, "ps PID" outputs
    # everything, and "ps -p PID" outputs the banner, and the process
    # line if alive.  In both cases it exits with success.
    #
    # We once used grep to check the result:
    #
    # ps -p $pid | grep ["^[	]*$pid[^0-9]"]
    #
    # Unfortunately sometimes there are flags displayed before the
    # process number.  Since we never saw a "ps -p PID" that does not
    # display the title line, we expect two lines.
    case $(ps -p $pid | wc -l | sed -e '[s/^[	 ]*//]') in
      1) # process is dead.
	 ;;
      2) # Process is live.
	 return 0;;
      *) error SOFTWARE "unexpected ps output:" "$(ps -p $pid)" ;;
    esac
  done
  return 1
}

# children_clean [CHILDREN]
# --------------------------
# Remove the children files.
children_clean ()
{
  test $[#] -ne 0 ||
    { set x $children; shift; }

  local i
  for i
  do
    rm -f $i.{cmd,pid,sta,in,out,err,val}
  done
}

# children_pid [CHILDREN]
# -----------------------
# Return the PIDs of the CHILDREN (whether alive or not).
children_pid ()
{
  test $[#] -ne 0 ||
    { set x $children; shift; }

  local i
  for i
  do
    test -f $i.pid ||
      error SOFTWARE "children_pid: cannot find $i.pid."
    cat $i.pid
  done
}

# children_register NAME
# ----------------------
# It is very important that we do not leave some random exit status
# from some previous runs.  Standard output etc. are not a problem:
# they *are* created during the run, while *.sta is created *only* if
# it does not already exists (this is because you can call "wait" only
# once per processus, so to enable calling children_harvest multiple
# times, we create *.sta only if it does not exist).
#
# Creates NAME.pid which contains the PID, and PID.name which contains
# the name.
children_register ()
{
  test $[#] -eq 1
  rm -f $[1].sta
  children="$children $[1]"
  echo $! >$[1].pid
  echo $[1] >$!.name
}

# children_report [CHILDREN]
# --------------------------
# Produce an RST report for the CHILDREN.
children_report ()
{
  test $[#] -ne 0 ||
    { set x $children; shift; }

  local i
  for i
  do
    rst_run_report "$i" "$i"
  done
}

# children_kill [CHILDREN]
# ------------------------
# Kill all the children.  This function can be called twice: once
# before cleaning the components, and once when exiting, so it's
# robust to children no longer in the process table.
children_kill ()
{
  local pids
  pids=$(children_pid "$[@]")
  pids_kill "$pids"
}

# children_harvest [CHILDREN]
# ---------------------------
# Report the exit status of the children.  Can be run several times.
# You might want to call it once in the regular control flow to fetch
# some exit status, but also in a trap, if you were interrupted.  So
# it is meant to be callable multiple times, which might be a danger
# wrt *.sta (cf., children_register).
children_harvest ()
{
  # Harvest exit status.
  test $[#] -ne 0 ||
    { set x $children; shift; }

  local i
  for i
  do
    # Don't wait for children we already waited for.
    if ! test -e $i.sta; then
      local pid
      pid=$(children_pid $i)
      # Beware of set -e.
      local sta
      if wait $pid 2>&1; then
	sta=$?
      else
	sta=$?
      fi
      echo "$sta$(ex_to_string $sta)" >$i.sta
    fi
  done
}

# children_status [CHILDREN]
# --------------------------
# Return the exit status of CHILD.  Must be run after harvesting.
# If several CHILD are given, return the highest exit status.
children_status ()
{
  test $[#] -ne 0 ||
    { set x $children; shift; }

  local res=0
  local i
  for i
  do
    # We need to have waited for these children.
    test -f $i.sta ||
      error SOFTWARE "children_status: cannot find $i.sta." \
		     "Was children_harvest called?"         \
		     "Maybe children_cleanup was already called..."
    local sta=$(sed -n '1{s/^\([[0-9][0-9]]*\).*/\1/;p;q;}' <$i.sta)
    if test $res -lt $sta; then
      res=$sta
    fi
  done
  echo $res
}

# children_wait TIMEOUT [CHILDREN]
# --------------------------------
# Wait for the registered children, and passed TIMEOUT, kill the remaining
# ones.  TIMEOUT is increased by 5 if instrumenting.
children_wait ()
{
  local timeout=$[1]
  shift
  local pids
  pids=$(children_pid "$[@]")
  pids_wait $timeout $pids
}

])

Fix quotation error.

Fix quotation error.

	* build-aux/children.as (children_alive): Fix m4 quotation.


package aerys.minko.render.material.phong
{
	import aerys.minko.render.Effect;
	import aerys.minko.render.material.basic.BasicMaterial;
	import aerys.minko.render.resource.texture.TextureResource;
	import aerys.minko.scene.node.Scene;
	import aerys.minko.type.binding.DataProvider;
	import aerys.minko.type.binding.IDataProvider;
	import aerys.minko.type.math.Vector4;

	import flash.utils.Dictionary;

	public class PhongMaterial extends BasicMaterial
	{
		public static const DEFAULT_NAME	: String		= 'PhongMaterial';

		private static const EFFECTS		: Dictionary	= new Dictionary(true);

		public function get receptionMask() : uint
		{
			return getProperty(PhongProperties.RECEPTION_MASK) as uint;
		}
		public function set receptionMask(value : uint) : void
		{
			setProperty(PhongProperties.RECEPTION_MASK, value);
		}

		public function get lightmap() : TextureResource
		{
			return getProperty(PhongProperties.LIGHT_MAP) as TextureResource;
		}
		public function set lightmap(value : TextureResource) : void
		{
			setProperty(PhongProperties.LIGHT_MAP, value);
		}

		public function get specularMap() : TextureResource
		{
			return getProperty(PhongProperties.SPECULAR_MAP) as TextureResource;
		}
		public function set specularMap(value : TextureResource) : void
		{
			setProperty(PhongProperties.SPECULAR_MAP, value);
		}

		public function get lightmapMultiplier() : Number
		{
			return getProperty(PhongProperties.LIGHTMAP_MULTIPLIER) as Number;
		}
		public function set lightmapMultiplier(value : Number) : void
		{
			setProperty(PhongProperties.LIGHTMAP_MULTIPLIER, value);
		}

		public function get ambientMultiplier() : Number
		{
			return getProperty(PhongProperties.AMBIENT_MULTIPLIER) as Number;
		}
		public function set ambientMultiplier(value : Number) : void
		{
			setProperty(PhongProperties.AMBIENT_MULTIPLIER, value);
		}

		public function get diffuseMultiplier() : Number
		{
			return getProperty(PhongProperties.DIFFUSE_MULTIPLIER) as Number;
		}
		public function set diffuseMultiplier(value : Number) : void
		{
			setProperty(PhongProperties.DIFFUSE_MULTIPLIER, value);
		}

		public function get specular() : Vector4
		{
			return getProperty(PhongProperties.SPECULAR) as Vector4;
		}

		public function set specular(value : Vector4) : void
		{
			setProperty(PhongProperties.SPECULAR, value);
		}

		public function get shininess() : Number
		{
			return getProperty(PhongProperties.SHININESS) as Number;
		}
		public function set shininess(value : Number) : void
		{
			setProperty(PhongProperties.SHININESS, value);
		}

		public function get normalMappingType() : uint
		{
			return getProperty(PhongProperties.NORMAL_MAPPING_TYPE) as uint;
		}
		public function set normalMappingType(value : uint) : void
		{
			setProperty(PhongProperties.NORMAL_MAPPING_TYPE, value);
		}

		public function get normalMap() : TextureResource
		{
			return getProperty(PhongProperties.NORMAL_MAP) as TextureResource;
		}
		public function set normalMap(value : TextureResource) : void
		{
			setProperty(PhongProperties.NORMAL_MAP, value);
		}

		public function get heightMap() : TextureResource
		{
			return getProperty(PhongProperties.HEIGHT_MAP) as TextureResource;
		}
		public function set heightMap(value : TextureResource) : void
		{
			setProperty(PhongProperties.HEIGHT_MAP, value);
		}

		public function get numParallaxMappingSteps() : uint
		{
			return getProperty(PhongProperties.PARALLAX_MAPPING_NBSTEPS) as uint;
		}
		public function set numParallaxMappingSteps(value : uint) : void
		{
			setProperty(PhongProperties.PARALLAX_MAPPING_NBSTEPS, value);
		}

		public function get parallaxMappingBumpScale() : Number
		{
			return getProperty(PhongProperties.PARALLAX_MAPPING_BUMP_SCALE) as Number;
		}
		public function set parallaxMappingBumpScale(value : Number) : void
		{
			setProperty(PhongProperties.PARALLAX_MAPPING_BUMP_SCALE, value);
		}

		public function get shadowBias() : Number
		{
			return getProperty(PhongProperties.SHADOW_BIAS) as Number;
		}
		public function set shadowBias(value : Number) : void
		{
			setProperty(PhongProperties.SHADOW_BIAS, value);
		}

		public function get castShadows() : Boolean
		{
			return getProperty(PhongProperties.CAST_SHADOWS) as Boolean;
		}
		public function set castShadows(value : Boolean) : void
		{
			setProperty(PhongProperties.CAST_SHADOWS, value);
		}

		public function get receiveShadows() : Boolean
		{
			return getProperty(PhongProperties.RECEIVE_SHADOWS) as Boolean;
		}
		public function set receiveShadows(value : Boolean) : void
		{
			setProperty(PhongProperties.RECEIVE_SHADOWS, value);
		}

		public function PhongMaterial(scene 		: Scene,
									  properties 	: Object 	= null,
									  effect		: Effect	= null,
									  name 			: String 	= DEFAULT_NAME)
		{
			super(
				properties,
				effect || (EFFECTS[scene] || (EFFECTS[scene] = new PhongEffect(scene))),
				name
			);
		}

		override public function clone() : IDataProvider
		{
			return new PhongMaterial((effect as PhongEffect).scene, this, effect, name);
		}
	}
}

package aerys.minko.render.material.phong
{
	import aerys.minko.render.Effect;
	import aerys.minko.render.material.basic.BasicMaterial;
	import aerys.minko.render.resource.texture.TextureResource;
	import aerys.minko.scene.node.Scene;
	import aerys.minko.type.binding.DataProvider;
	import aerys.minko.type.binding.IDataProvider;
	import aerys.minko.type.math.Vector4;

	import flash.utils.Dictionary;

	public class PhongMaterial extends BasicMaterial
	{
		public static const DEFAULT_NAME	: String		= 'PhongMaterial';

		private static const EFFECTS		: Dictionary	= new Dictionary(true);

		public function get receptionMask() : uint
		{
			return getProperty(PhongProperties.RECEPTION_MASK) as uint;
		}
		public function set receptionMask(value : uint) : void
		{
			setProperty(PhongProperties.RECEPTION_MASK, value);
		}

		public function get lightmap() : TextureResource
		{
			return getProperty(PhongProperties.LIGHT_MAP) as TextureResource;
		}
		public function set lightmap(value : TextureResource) : void
		{
			setProperty(PhongProperties.LIGHT_MAP, value);
		}

		public function get specularMap() : TextureResource
		{
			return getProperty(PhongProperties.SPECULAR_MAP) as TextureResource;
		}
		public function set specularMap(value : TextureResource) : void
		{
			setProperty(PhongProperties.SPECULAR_MAP, value);
		}

		public function get lightmapMultiplier() : Number
		{
			return getProperty(PhongProperties.LIGHTMAP_MULTIPLIER) as Number;
		}
		public function set lightmapMultiplier(value : Number) : void
		{
			setProperty(PhongProperties.LIGHTMAP_MULTIPLIER, value);
		}

		public function get ambientMultiplier() : Number
		{
			return getProperty(PhongProperties.AMBIENT_MULTIPLIER) as Number;
		}
		public function set ambientMultiplier(value : Number) : void
		{
			setProperty(PhongProperties.AMBIENT_MULTIPLIER, value);
		}

		public function get diffuseMultiplier() : Number
		{
			return getProperty(PhongProperties.DIFFUSE_MULTIPLIER) as Number;
		}
		public function set diffuseMultiplier(value : Number) : void
		{
			setProperty(PhongProperties.DIFFUSE_MULTIPLIER, value);
		}

		public function get specular() : Vector4
		{
			return getProperty(PhongProperties.SPECULAR) as Vector4;
		}

		public function set specular(value : Vector4) : void
		{
			setProperty(PhongProperties.SPECULAR, value);
		}

		public function get shininess() : Number
		{
			return getProperty(PhongProperties.SHININESS) as Number;
		}
		public function set shininess(value : Number) : void
		{
			setProperty(PhongProperties.SHININESS, value);
		}

		public function get normalMappingType() : uint
		{
			return getProperty(PhongProperties.NORMAL_MAPPING_TYPE) as uint;
		}
		public function set normalMappingType(value : uint) : void
		{
			setProperty(PhongProperties.NORMAL_MAPPING_TYPE, value);
		}

		public function get normalMap() : TextureResource
		{
			return getProperty(PhongProperties.NORMAL_MAP) as TextureResource;
		}
		public function set normalMap(value : TextureResource) : void
		{
			setProperty(PhongProperties.NORMAL_MAP, value);
		}

        public function get normalMapFiltering() : uint
        {
            return getProperty(PhongProperties.NORMAL_MAP_FILTERING);
        }
        public function set normalMapFiltering(value : uint) : void
        {
            setProperty(PhongProperties.NORMAL_MAP_FILTERING, value);
        }

        public function get normalMapMipMapping() : uint
        {
            return getProperty(PhongProperties.NORMAL_MAP_MIPMAPPING);
        }
        public function set normalMapMipMapping(value : uint) : void
        {
            setProperty(PhongProperties.NORMAL_MAP_MIPMAPPING, value);
        }

        public function get normalMapFormat() : uint
        {
            return getProperty(PhongProperties.NORMAL_MAP_FORMAT);
        }
        public function set normalMapFormat(value : uint) : void
        {
            setProperty(PhongProperties.NORMAL_MAP_FORMAT, value);
        }

		public function get heightMap() : TextureResource
		{
			return getProperty(PhongProperties.HEIGHT_MAP) as TextureResource;
		}
		public function set heightMap(value : TextureResource) : void
		{
			setProperty(PhongProperties.HEIGHT_MAP, value);
		}

		public function get numParallaxMappingSteps() : uint
		{
			return getProperty(PhongProperties.PARALLAX_MAPPING_NBSTEPS) as uint;
		}
		public function set numParallaxMappingSteps(value : uint) : void
		{
			setProperty(PhongProperties.PARALLAX_MAPPING_NBSTEPS, value);
		}

		public function get parallaxMappingBumpScale() : Number
		{
			return getProperty(PhongProperties.PARALLAX_MAPPING_BUMP_SCALE) as Number;
		}
		public function set parallaxMappingBumpScale(value : Number) : void
		{
			setProperty(PhongProperties.PARALLAX_MAPPING_BUMP_SCALE, value);
		}

		public function get shadowBias() : Number
		{
			return getProperty(PhongProperties.SHADOW_BIAS) as Number;
		}
		public function set shadowBias(value : Number) : void
		{
			setProperty(PhongProperties.SHADOW_BIAS, value);
		}

		public function get castShadows() : Boolean
		{
			return getProperty(PhongProperties.CAST_SHADOWS) as Boolean;
		}
		public function set castShadows(value : Boolean) : void
		{
			setProperty(PhongProperties.CAST_SHADOWS, value);
		}

		public function get receiveShadows() : Boolean
		{
			return getProperty(PhongProperties.RECEIVE_SHADOWS) as Boolean;
		}
		public function set receiveShadows(value : Boolean) : void
		{
			setProperty(PhongProperties.RECEIVE_SHADOWS, value);
		}

		public function PhongMaterial(scene 		: Scene,
									  properties 	: Object 	= null,
									  effect		: Effect	= null,
									  name 			: String 	= DEFAULT_NAME)
		{
			super(
				properties,
				effect || (EFFECTS[scene] || (EFFECTS[scene] = new PhongEffect(scene))),
				name
			);
		}

		override public function clone() : IDataProvider
		{
			return new PhongMaterial((effect as PhongEffect).scene, this, effect, name);
		}
	}
}

add public properties normalMapFiltering, normalMapMipMapping and normalMapFormat to PhongMaterial


//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

package com.threerings.util {

import flash.utils.ByteArray;

import flash.utils.describeType; // function import

import mx.utils.*;

public class StringUtil
{
    /**
     * Get a reasonable hash code for the specified String.
     */
    public static function hashCode (str :String) :int
    {
        var code :int = 0;
        if (str != null) {
            // sample at most 8 chars
            var lastChar :int = Math.min(8, str.length);
            for (var ii :int = 0; ii < lastChar; ii++) {
                code = code * 31 + str.charCodeAt(ii);
            }
        }
        return code;
    }

    public static function isBlank (str :String) :Boolean
    {
        return (str == null) || (str.search("\\S") == -1);
    }

    /**
     * Does the specified string end with the specified substring.
     */
    public static function endsWith (str :String, substr :String) :Boolean
    {
        var startDex :int = str.length - substr.length;
        return (startDex >= 0) && (str.indexOf(substr, startDex) >= 0);
    }

    /**
     * Does the specified string start with the specified substring.
     */
    public static function startsWith (str :String, substr :String) :Boolean
    {
        // just check once if it's at the beginning
        return (str.lastIndexOf(substr, 0) == 0);
    }

    /**
     * Return true iff the first character is a lower-case character.
     */
    public static function isLowerCase (str :String) :Boolean
    {
        var firstChar :String = str.charAt(0);
        return (firstChar.toUpperCase() != firstChar) &&
            (firstChar.toLowerCase() == firstChar);
    }

    /**
     * Return true iff the first character is an upper-case character.
     */
    public static function isUpperCase (str :String) :Boolean
    {
        var firstChar :String = str.charAt(0);
        return (firstChar.toUpperCase() == firstChar) &&
            (firstChar.toLowerCase() != firstChar);
    }

    /**
     * Parse an integer more anally than the built-in parseInt() function,
     * throwing an ArgumentError if there are any invalid characters.
     *
     * The built-in parseInt() will ignore trailing non-integer characters.
     *
     * @param str The string to parse.
     * @param radix The radix to use, from 2 to 16. If not specified the radix will be 10,
     *        unless the String begins with "0x" in which case it will be 16,
     *        or the String begins with "0" in which case it will be 8.
     */
    public static function parseInteger (str :String, radix :uint = 0) :int
    {
        if (str == null) {
            throw new ArgumentError("Cannot parseInt(null)");
        }

        if (radix == 0) {
            if (startsWith(str, "0x")) {
                str = str.substring(2);
                radix = 16;

            } else if (startsWith(str, "0")) {
                str = str.substring(1);
                radix = 8;

            } else {
                radix = 10;
            }

        } else if (radix == 16 && startsWith(str, "0x")) {
            str = str.substring(2);

        } else if (radix < 2 || radix > 16) {
            throw new ArgumentError("Radix out of range: " + radix);
        }

        // now verify that str only contains valid chars for the radix
        for (var ii :int = 0; ii < str.length; ii++) {
            var dex :int = HEX.indexOf(str.charAt(ii).toLowerCase());
            if (dex == -1 || dex >= radix) {
                throw new ArgumentError("Invalid characters in String parseInt='" + arguments[0] +
                    "', radix=" + radix);
            }
        }

        var result :Number = parseInt(str, radix);
        if (isNaN(result)) {
            // this shouldn't happen..
            throw new ArgumentError("Could not parseInt=" + arguments[0]);
        }
        return int(result);
    }

    /**
     * Append 0 or more copies of the padChar String to the input String
     * until it is at least the specified length.
     */
    public static function pad (
        str :String, length :int, padChar :String = " ") :String
    {
        while (str.length < length) {
            str += padChar;
        }
        return str;
    }

    /**
     * Prepend 0 or more copies of the padChar String to the input String
     * until it is at least the specified length.
     */
    public static function prepad (
        str :String, length :int, padChar :String = " ") :String
    {
        while (str.length < length) {
            str = padChar + str;
        }
        return str;
    }

    /**
     * Substitute "{n}" tokens for the corresponding passed-in arguments.
     */
    public static function substitute (str :String, ... args) :String
    {
        // holy christ the varargs insanity
        args = Util.unfuckVarargs(args);
        args.unshift(str);
        return mx.utils.StringUtil.substitute.apply(null, args);
    }

    /**
     * Utility function that strips whitespace from the ends of a String.
     */
    public static function trim (str :String) :String
    {
        return mx.utils.StringUtil.trim(str);
    }

    public static function toString (obj :Object) :String
    {
        if (obj is Array) {
            var arr :Array = (obj as Array);
            var s :String = "Array(";
            for (var ii :int = 0; ii < arr.length; ii++) {
                if (ii > 0) {
                    s += ", ";
                }
                s += (ii + ": " + toString(arr[ii]));
            }
            return s + ")";
        }

        return String(obj);
    }

    /**
     * Return a string containing all the public fields of the object
     */
    public static function fieldsToString (buf :StringBuilder, obj :Object) :void
    {
        var desc :XML = describeType(obj);
        var appended :Boolean = false;
        for each (var varName :String in desc..variable.@name) {
            if (appended) {
                buf.append(", ");
            }
            buf.append(varName, "=", obj[varName]);
            appended = true;
        }
    }

    /**
     * Return a pretty basic toString of the supplied Object.
     */
    public static function simpleToString (obj :Object) :String
    {
        var buf :StringBuilder = new StringBuilder("[");
        buf.append(ClassUtil.tinyClassName(obj));
        buf.append("(");
        fieldsToString(buf, obj);
        return buf.append(")]").toString();
    }

    /**
     * Truncate the specified String if it is longer than maxLength.
     * The string will be truncated at a position such that it is
     * maxLength chars long after the addition of the 'append' String.
     *
     * @param append a String to add to the truncated String only after
     * truncation.
     */
    public static function truncate (
        s :String, maxLength :int, append :String = "") :String
    {
        if ((s == null) || (s.length <= maxLength)) {
            return s;
        } else {
            return s.substring(0, maxLength - append.length) + append;
        }
    }

    /**
     * Locate URLs in a string, return an array in which even elements
     * are plain text, odd elements are urls (as Strings). Any even element
     * may be an empty string.
     */
    public static function parseURLs (s :String) :Array
    {
        var array :Array = [];
        while (true) {
            var result :Object = URL_REGEXP.exec(s);
            if (result == null) {
                break;
            }

            var index :int = int(result.index);
            var url :String = String(result[0]);
            array.push(s.substring(0, index), url);
            s = s.substring(index + url.length);
        }

        // just the string is left
        array.push(s);
        return array;
    }

    /**
     * Turn the specified byte array, containing only ascii characters, into a String.
     */
    public static function fromBytes (bytes :ByteArray) :String
    {
        var s :String = "";
        if (bytes != null) {
            for (var ii :int = 0; ii < bytes.length; ii++) {
                s += String.fromCharCode(bytes[ii]);
            }
        }
        return s;
    }

    /**
     * Turn the specified String, containing only ascii characters, into a ByteArray.
     */
    public static function toBytes (s :String) :ByteArray
    {
        if (s == null) {
            return null;
        }
        var ba :ByteArray = new ByteArray();
        for (var ii :int = 0; ii < s.length; ii++) {
            ba[ii] = int(s.charCodeAt(ii)) & 0xFF;
        }
        return ba;
    }

    /**
     * Generates a string from the supplied bytes that is the hex encoded
     * representation of those byts. Returns the empty String for a
     * null or empty byte array.
     */
    public static function hexlate (bytes :ByteArray) :String
    {
        var str :String = "";
        if (bytes != null) {
            for (var ii :int = 0; ii < bytes.length; ii++) {
                var b :int = bytes[ii];
                str += HEX[b >> 4] + HEX[b & 0xF];
            }
        }
        return str;
    }

    /**
     * Turn a hexlated String back into a ByteArray.
     */
    public static function unhexlate (hex :String) :ByteArray
    {
        if (hex == null || (hex.length % 2 != 0)) {
            return null;
        }

        hex = hex.toLowerCase();
        var data :ByteArray = new ByteArray();
        for (var ii :int = 0; ii < hex.length; ii += 2) {
            var value :int = HEX.indexOf(hex.charAt(ii)) << 4;
            value += HEX.indexOf(hex.charAt(ii + 1));

            // TODO: verify
            // values over 127 are wrapped around, restoring negative bytes
            data[ii / 2] = value;
        }

        return data;
    }

    /** Hexidecimal digits. */
    protected static const HEX :Array = [ "0", "1", "2", "3", "4",
        "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" ];

    /** A regular expression that finds URLs. */
    protected static const URL_REGEXP :RegExp =
        new RegExp("(http|https|ftp)://\\S+", "i");
}
}

//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

package com.threerings.util {

import flash.utils.ByteArray;

import flash.utils.describeType; // function import

import mx.utils.*;

public class StringUtil
{
    /**
     * Get a reasonable hash code for the specified String.
     */
    public static function hashCode (str :String) :int
    {
        var code :int = 0;
        if (str != null) {
            // sample at most 8 chars
            var lastChar :int = Math.min(8, str.length);
            for (var ii :int = 0; ii < lastChar; ii++) {
                code = code * 31 + str.charCodeAt(ii);
            }
        }
        return code;
    }

    public static function isBlank (str :String) :Boolean
    {
        return (str == null) || (str.search("\\S") == -1);
    }

    /**
     * Does the specified string end with the specified substring.
     */
    public static function endsWith (str :String, substr :String) :Boolean
    {
        var startDex :int = str.length - substr.length;
        return (startDex >= 0) && (str.indexOf(substr, startDex) >= 0);
    }

    /**
     * Does the specified string start with the specified substring.
     */
    public static function startsWith (str :String, substr :String) :Boolean
    {
        // just check once if it's at the beginning
        return (str.lastIndexOf(substr, 0) == 0);
    }

    /**
     * Return true iff the first character is a lower-case character.
     */
    public static function isLowerCase (str :String) :Boolean
    {
        var firstChar :String = str.charAt(0);
        return (firstChar.toUpperCase() != firstChar) &&
            (firstChar.toLowerCase() == firstChar);
    }

    /**
     * Return true iff the first character is an upper-case character.
     */
    public static function isUpperCase (str :String) :Boolean
    {
        var firstChar :String = str.charAt(0);
        return (firstChar.toUpperCase() == firstChar) &&
            (firstChar.toLowerCase() != firstChar);
    }

    /**
     * Parse an integer more anally than the built-in parseInt() function,
     * throwing an ArgumentError if there are any invalid characters.
     *
     * The built-in parseInt() will ignore trailing non-integer characters.
     *
     * @param str The string to parse.
     * @param radix The radix to use, from 2 to 16. If not specified the radix will be 10,
     *        unless the String begins with "0x" in which case it will be 16,
     *        or the String begins with "0" in which case it will be 8.
     */
    public static function parseInteger (str :String, radix :uint = 0) :int
    {
        if (str == null) {
            throw new ArgumentError("Cannot parseInt(null)");
        }

        if (radix == 0) {
            if (startsWith(str, "0x")) {
                str = str.substring(2);
                radix = 16;

            } else if (startsWith(str, "0")) {
                str = str.substring(1);
                radix = 8;

            } else {
                radix = 10;
            }

        } else if (radix == 16 && startsWith(str, "0x")) {
            str = str.substring(2);

        } else if (radix < 2 || radix > 16) {
            throw new ArgumentError("Radix out of range: " + radix);
        }

        // now verify that str only contains valid chars for the radix
        for (var ii :int = 0; ii < str.length; ii++) {
            var dex :int = HEX.indexOf(str.charAt(ii).toLowerCase());
            if (dex == -1 || dex >= radix) {
                throw new ArgumentError("Invalid characters in String parseInt='" + arguments[0] +
                    "', radix=" + radix);
            }
        }

        var result :Number = parseInt(str, radix);
        if (isNaN(result)) {
            // this shouldn't happen..
            throw new ArgumentError("Could not parseInt=" + arguments[0]);
        }
        return int(result);
    }

    /**
     * Append 0 or more copies of the padChar String to the input String
     * until it is at least the specified length.
     */
    public static function pad (
        str :String, length :int, padChar :String = " ") :String
    {
        while (str.length < length) {
            str += padChar;
        }
        return str;
    }

    /**
     * Prepend 0 or more copies of the padChar String to the input String
     * until it is at least the specified length.
     */
    public static function prepad (
        str :String, length :int, padChar :String = " ") :String
    {
        while (str.length < length) {
            str = padChar + str;
        }
        return str;
    }

    /**
     * Substitute "{n}" tokens for the corresponding passed-in arguments.
     */
    public static function substitute (str :String, ... args) :String
    {
        // holy christ the varargs insanity
        args = Util.unfuckVarargs(args);
        args.unshift(str);
        return mx.utils.StringUtil.substitute.apply(null, args);
    }

    /**
     * Utility function that strips whitespace from the ends of a String.
     */
    public static function trim (str :String) :String
    {
        return mx.utils.StringUtil.trim(str);
    }

    public static function toString (obj :*) :String
    {
        if (obj is Array) {
            var arr :Array = (obj as Array);
            var s :String = "Array(";
            for (var ii :int = 0; ii < arr.length; ii++) {
                if (ii > 0) {
                    s += ", ";
                }
                s += (ii + ": " + toString(arr[ii]));
            }
            return s + ")";
        }

        return String(obj);
    }

    /**
     * Return a string containing all the public fields of the object
     */
    public static function fieldsToString (buf :StringBuilder, obj :Object) :void
    {
        var desc :XML = describeType(obj);
        var appended :Boolean = false;
        for each (var varName :String in desc..variable.@name) {
            if (appended) {
                buf.append(", ");
            }
            buf.append(varName, "=", obj[varName]);
            appended = true;
        }
    }

    /**
     * Return a pretty basic toString of the supplied Object.
     */
    public static function simpleToString (obj :Object) :String
    {
        var buf :StringBuilder = new StringBuilder("[");
        buf.append(ClassUtil.tinyClassName(obj));
        buf.append("(");
        fieldsToString(buf, obj);
        return buf.append(")]").toString();
    }

    /**
     * Truncate the specified String if it is longer than maxLength.
     * The string will be truncated at a position such that it is
     * maxLength chars long after the addition of the 'append' String.
     *
     * @param append a String to add to the truncated String only after
     * truncation.
     */
    public static function truncate (
        s :String, maxLength :int, append :String = "") :String
    {
        if ((s == null) || (s.length <= maxLength)) {
            return s;
        } else {
            return s.substring(0, maxLength - append.length) + append;
        }
    }

    /**
     * Locate URLs in a string, return an array in which even elements
     * are plain text, odd elements are urls (as Strings). Any even element
     * may be an empty string.
     */
    public static function parseURLs (s :String) :Array
    {
        var array :Array = [];
        while (true) {
            var result :Object = URL_REGEXP.exec(s);
            if (result == null) {
                break;
            }

            var index :int = int(result.index);
            var url :String = String(result[0]);
            array.push(s.substring(0, index), url);
            s = s.substring(index + url.length);
        }

        // just the string is left
        array.push(s);
        return array;
    }

    /**
     * Turn the specified byte array, containing only ascii characters, into a String.
     */
    public static function fromBytes (bytes :ByteArray) :String
    {
        var s :String = "";
        if (bytes != null) {
            for (var ii :int = 0; ii < bytes.length; ii++) {
                s += String.fromCharCode(bytes[ii]);
            }
        }
        return s;
    }

    /**
     * Turn the specified String, containing only ascii characters, into a ByteArray.
     */
    public static function toBytes (s :String) :ByteArray
    {
        if (s == null) {
            return null;
        }
        var ba :ByteArray = new ByteArray();
        for (var ii :int = 0; ii < s.length; ii++) {
            ba[ii] = int(s.charCodeAt(ii)) & 0xFF;
        }
        return ba;
    }

    /**
     * Generates a string from the supplied bytes that is the hex encoded
     * representation of those byts. Returns the empty String for a
     * null or empty byte array.
     */
    public static function hexlate (bytes :ByteArray) :String
    {
        var str :String = "";
        if (bytes != null) {
            for (var ii :int = 0; ii < bytes.length; ii++) {
                var b :int = bytes[ii];
                str += HEX[b >> 4] + HEX[b & 0xF];
            }
        }
        return str;
    }

    /**
     * Turn a hexlated String back into a ByteArray.
     */
    public static function unhexlate (hex :String) :ByteArray
    {
        if (hex == null || (hex.length % 2 != 0)) {
            return null;
        }

        hex = hex.toLowerCase();
        var data :ByteArray = new ByteArray();
        for (var ii :int = 0; ii < hex.length; ii += 2) {
            var value :int = HEX.indexOf(hex.charAt(ii)) << 4;
            value += HEX.indexOf(hex.charAt(ii + 1));

            // TODO: verify
            // values over 127 are wrapped around, restoring negative bytes
            data[ii / 2] = value;
        }

        return data;
    }

    /** Hexidecimal digits. */
    protected static const HEX :Array = [ "0", "1", "2", "3", "4",
        "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" ];

    /** A regular expression that finds URLs. */
    protected static const URL_REGEXP :RegExp =
        new RegExp("(http|https|ftp)://\\S+", "i");
}
}

Allow undefined array elements to print as "undefined" rather than "null".

git-svn-id: a1a4b28b82a3276cc491891159dd9963a0a72fae@4749 542714f4-19e9-0310-aa3c-eee0fc999fb1