/* ***** 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 weave.data.DataSources
{
	import flash.events.ErrorEvent;

	import mx.rpc.events.FaultEvent;
	import mx.rpc.events.ResultEvent;

	import weave.api.core.ILinkableObject;
	import weave.api.data.ColumnMetadata;
	import weave.api.data.IAttributeColumn;
	import weave.api.data.IColumnReference;
	import weave.api.data.IDataSource;
	import weave.api.data.IDataSource_Service;
	import weave.api.data.IWeaveTreeNode;
	import weave.api.detectLinkableObjectChange;
	import weave.api.disposeObject;
	import weave.api.getCallbackCollection;
	import weave.api.getLinkableDescendants;
	import weave.api.getSessionState;
	import weave.api.linkableObjectIsBusy;
	import weave.api.newLinkableChild;
	import weave.api.registerLinkableChild;
	import weave.api.reportError;
	import weave.core.LinkableHashMap;
	import weave.core.LinkablePromise;
	import weave.core.LinkableString;
	import weave.data.AttributeColumns.ProxyColumn;
	import weave.data.AttributeColumns.ReferencedColumn;
	import weave.data.hierarchy.ColumnTreeNode;
	import weave.services.AMF3Servlet;
	import weave.services.JsonCache;
	import weave.services.WeaveRServlet;
	import weave.services.addAsyncResponder;
	import weave.utils.ColumnUtils;
	import weave.utils.VectorUtils;

	public class RDataSource extends AbstractDataSource implements IDataSource_Service
	{
		WeaveAPI.ClassRegistry.registerImplementation(IDataSource, RDataSource, "R Data Source");

		public static const SCRIPT_OUTPUT_NAME:String = 'scriptOutputName';
		private static const RESULT_DATA:String = "resultData";
		private static const COLUMN_NAMES:String = "columnNames";

		public function RDataSource()
		{
			promise.depend(url, scriptName, inputs, hierarchyRefresh);
		}

		public const url:LinkableString = registerLinkableChild(this, new LinkableString('/WeaveAnalystServices/ComputationalServlet'), handleURLChange);
		public const scriptName:LinkableString = newLinkableChild(this, LinkableString);
		public const inputs:LinkableHashMap = newLinkableChild(this, LinkableHashMap);

		private const promise:LinkablePromise = registerLinkableChild(this, new LinkablePromise(runScript, describePromise));
		private function describePromise():String { return lang("Running script {0}", scriptName.value); }
		private const outputCSV:CSVDataSource = newLinkableChild(this, CSVDataSource);
		private var _service:AMF3Servlet;

		override protected function initialize():void
		{
			super.initialize();
			promise.validate();
		}

		private function handleURLChange():void
		{
			if (_service && _service.servletURL == url.value)
				return;
			disposeObject(_service);
			_service = registerLinkableChild(this, new AMF3Servlet(url.value));
			hierarchyRefresh.triggerCallbacks();
		}

		private function runScript():void
		{
			// if callbacks are delayed, we assume that hierarchyRefresh will be explicitly triggered later.
			if (getCallbackCollection(this).callbacksAreDelayed)
				return;
			// force retrieval of referenced columns
			for each (var refCol:ReferencedColumn in getLinkableDescendants(inputs, ReferencedColumn))
				refCol.getInternalColumn();
			if (linkableObjectIsBusy(inputs))
				return;

			var keyType:String;
			var simpleInputs:Object = {};
			var columnsByKeyType:Object = {}; // Object(keyType -> Array of IAttributeColumn)

			var tableData:Object = {}; // Object(inputName -> { columnName : column })
			var columnData:Object = {}; // Object(keyType -> {keys: [], columns: Object(name -> Array) })

			var joined:Array = [];
			var colMap:Object = {};

			for each (var obj:ILinkableObject in inputs.getObjects())
			{
				var name:String = inputs.getName(obj);

				// if the input is a column
				var column:IAttributeColumn = obj as IAttributeColumn;
				if (column)
				{
					keyType = column.getMetadata(ColumnMetadata.KEY_TYPE);
					var columnArray:Array = columnsByKeyType[keyType] || (columnsByKeyType[keyType] = [])
					columnArray.push(column);
					for (keyType in columnsByKeyType)
					{
						var cols:Array = columnsByKeyType[keyType];
						joined = ColumnUtils.joinColumns(cols, null, true);
						var keys:Array = VectorUtils.pluck(joined.shift() as Array, 'localName');
						colMap = {};
						for (var i:int = 0; i <  joined.length; i++)
							colMap[ inputs.getName(cols[i]) ] = joined[i];
						columnData[keyType] = {keys: keys, columns: colMap};
					}
				}
				else
				{
					var columns:Array = obj as Array;
					// if the input is an array of columns
					if(columns)
					{
						var flag:Boolean = false;
						if(!columns.length)
							return;

						column = columns[0] as IAttributeColumn;
						keyType = column.getMetadata(ColumnMetadata.KEY_TYPE);

						for each (var col:IAttributeColumn in columns)
						{
							flag = col.getMetadata(ColumnMetadata.KEY_TYPE) != keyType;
							colMap[col.getMetadata(ColumnMetadata.TITLE)] = col;
						}
						if(flag) {
							reportError("Columns in table input must all be of the same keyType");
							return;
						} else {
							tableData[name] = colMap;
						}
					} else {
						// if the input is just a value (number, string or boolean)
						simpleInputs[name] = getSessionState(inputs.getObject(name));
					}
				}
			}

			outputCSV.setCSVData(null);
			outputCSV.metadata.setSessionState(null);

			addAsyncResponder(
				_service.invokeAsyncMethod('runScriptWithInputs', [scriptName.value, simpleInputs, columnData, tableData]),
				handleScriptResult,
				handleScriptError,
				getSessionState(this)
			);
		}

		private function handleScriptResult(event:ResultEvent, sessionState:Object):void
		{
			// ignore outdated response
			if (WeaveAPI.SessionManager.computeDiff(sessionState, getSessionState(this)) !== undefined)
				return;

			var result:Object = event.result;
			var columnNames:Array = result[COLUMN_NAMES]; // array of strings
			var resultData:Array = result[RESULT_DATA]; // array of columns
			if (resultData)
			{
				var rows:Array = VectorUtils.transpose(resultData);
				rows.unshift(columnNames);
				outputCSV.setCSVData(rows);
			}
			else
				outputCSV.setCSVData(null);
		}

		private function handleScriptError(event:FaultEvent, sessionState:Object):void
		{
			// ignore outdated response
			if (WeaveAPI.SessionManager.computeDiff(sessionState, getSessionState(this)) !== undefined)
				return;
			reportError(event);
		}

		override public function getHierarchyRoot():IWeaveTreeNode
		{
			if (!_rootNode)
			{
				var name:String = WeaveAPI.globalHashMap.getName(this);
				var source:RDataSource = this;
				_rootNode = new ColumnTreeNode({
					label: name,
					dataSource: this,
					dependency: outputCSV,
					hasChildBranches: false,
					children: function():Array {
						var csvRoot:IWeaveTreeNode = outputCSV.getHierarchyRoot();
						return csvRoot.getChildren().map(function(csvNode:IColumnReference, ..._):IWeaveTreeNode {
							var meta:Object = csvNode.getColumnMetadata();
							meta[SCRIPT_OUTPUT_NAME] = meta[CSVDataSource.METADATA_COLUMN_NAME];
							return generateHierarchyNode(meta);
						});
					}
				});
			}
			return _rootNode;
		}

		override protected function generateHierarchyNode(metadata:Object):IWeaveTreeNode
		{
			if (!metadata)
				return null;

			return new ColumnTreeNode({
				dataSource: this,
				idFields: [SCRIPT_OUTPUT_NAME],
				data: metadata
			});
		}

		override protected function requestColumnFromSource(proxyColumn:ProxyColumn):void
		{
			var name:String = proxyColumn.getMetadata(SCRIPT_OUTPUT_NAME);
			var column:IAttributeColumn = outputCSV.getColumnById(name);
			if (column)
				proxyColumn.setInternalColumn(column);
			else
				proxyColumn.dataUnavailable();
		}
	}
}

/* ***** 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 weave.data.DataSources
{
	import flash.events.ErrorEvent;

	import mx.rpc.events.FaultEvent;
	import mx.rpc.events.ResultEvent;

	import weave.api.core.ILinkableObject;
	import weave.api.data.ColumnMetadata;
	import weave.api.data.IAttributeColumn;
	import weave.api.data.IColumnReference;
	import weave.api.data.IDataSource;
	import weave.api.data.IDataSource_Service;
	import weave.api.data.IWeaveTreeNode;
	import weave.api.detectLinkableObjectChange;
	import weave.api.disposeObject;
	import weave.api.getCallbackCollection;
	import weave.api.getLinkableDescendants;
	import weave.api.getSessionState;
	import weave.api.linkableObjectIsBusy;
	import weave.api.newLinkableChild;
	import weave.api.registerLinkableChild;
	import weave.api.reportError;
	import weave.core.LinkableHashMap;
	import weave.core.LinkablePromise;
	import weave.core.LinkableString;
	import weave.data.AttributeColumns.ProxyColumn;
	import weave.data.AttributeColumns.ReferencedColumn;
	import weave.data.hierarchy.ColumnTreeNode;
	import weave.services.AMF3Servlet;
	import weave.services.JsonCache;
	import weave.services.WeaveRServlet;
	import weave.services.addAsyncResponder;
	import weave.utils.ColumnUtils;
	import weave.utils.VectorUtils;

	public class RDataSource extends AbstractDataSource implements IDataSource_Service
	{
		WeaveAPI.ClassRegistry.registerImplementation(IDataSource, RDataSource, "R Data Source");

		public static const SCRIPT_OUTPUT_NAME:String = 'scriptOutputName';
		private static const RESULT_DATA:String = "resultData";
		private static const COLUMN_NAMES:String = "columnNames";

		public function RDataSource()
		{
			promise.depend(url, scriptName, inputs, hierarchyRefresh);
		}

		public const url:LinkableString = registerLinkableChild(this, new LinkableString('/WeaveAnalystServices/ComputationalServlet'), handleURLChange);
		public const scriptName:LinkableString = newLinkableChild(this, LinkableString);
		public const inputs:LinkableHashMap = newLinkableChild(this, LinkableHashMap);

		private const promise:LinkablePromise = registerLinkableChild(this, new LinkablePromise(runScript, describePromise));
		private function describePromise():String { return lang("Running script {0}", scriptName.value); }
		private const outputCSV:CSVDataSource = newLinkableChild(this, CSVDataSource);
		private var _service:AMF3Servlet;

		override protected function initialize():void
		{
			super.initialize();
			promise.validate();
		}

		private function handleURLChange():void
		{
			if (_service && _service.servletURL == url.value)
				return;
			disposeObject(_service);
			_service = registerLinkableChild(this, new AMF3Servlet(url.value));
			hierarchyRefresh.triggerCallbacks();
		}

		private function runScript():void
		{
			// if callbacks are delayed, we assume that hierarchyRefresh will be explicitly triggered later.
			if (getCallbackCollection(this).callbacksAreDelayed)
				return;
			// force retrieval of referenced columns
			for each (var refCol:ReferencedColumn in getLinkableDescendants(inputs, ReferencedColumn))
			refCol.getInternalColumn();
			if (linkableObjectIsBusy(inputs))
				return;

			var keyType:String;
			var simpleInputs:Object = {};
			var columnsByKeyType:Object = {}; // Object(keyType -> Array of IAttributeColumn)
			for each (var obj:ILinkableObject in inputs.getObjects())
			{
				var name:String = inputs.getName(obj);
				var column:IAttributeColumn = obj as IAttributeColumn;
				if (column)
				{
					keyType = column.getMetadata(ColumnMetadata.KEY_TYPE);
					var columnArray:Array = columnsByKeyType[keyType] || (columnsByKeyType[keyType] = [])
					columnArray.push(column);
				}
				else
				{
					simpleInputs[name] = getSessionState(inputs.getObject(name));
				}
			}

			var columnData:Object = {}; // Object(keyType -> {keys: [], columns: Object(name -> Array) })
			for (keyType in columnsByKeyType)
			{
				var cols:Array = columnsByKeyType[keyType];
				var joined:Array = ColumnUtils.joinColumns(cols, null, true);
				var keys:Array = VectorUtils.pluck(joined.shift() as Array, 'localName');
				var colMap:Object = {};
				for (var i:int = 0; i <  joined.length; i++)
					colMap[ inputs.getName(cols[i]) ] = joined[i];
				columnData[keyType] = {keys: keys, columns: colMap};
			}

			outputCSV.setCSVData(null);
			outputCSV.metadata.setSessionState(null);

			addAsyncResponder(
				_service.invokeAsyncMethod('runScriptWithInputs', [scriptName.value, simpleInputs, columnData]),
				handleScriptResult,
				handleScriptError,
				getSessionState(this)
			);
		}

		private function handleScriptResult(event:ResultEvent, sessionState:Object):void
		{
			// ignore outdated response
			if (WeaveAPI.SessionManager.computeDiff(sessionState, getSessionState(this)) !== undefined)
				return;

			var result:Object = event.result;
			var columnNames:Array = result[COLUMN_NAMES]; // array of strings
			var resultData:Array = result[RESULT_DATA]; // array of columns
			if (resultData)
			{
				var rows:Array = VectorUtils.transpose(resultData);
				rows.unshift(columnNames);
				outputCSV.setCSVData(rows);
			}
			else
				outputCSV.setCSVData(null);
		}

		private function handleScriptError(event:FaultEvent, sessionState:Object):void
		{
			// ignore outdated response
			if (WeaveAPI.SessionManager.computeDiff(sessionState, getSessionState(this)) !== undefined)
				return;
			reportError(event);
		}

		override public function getHierarchyRoot():IWeaveTreeNode
		{
			if (!_rootNode)
			{
				var name:String = WeaveAPI.globalHashMap.getName(this);
				var source:RDataSource = this;
				_rootNode = new ColumnTreeNode({
					label: name,
					dataSource: this,
					dependency: outputCSV,
					hasChildBranches: false,
					children: function():Array {
						var csvRoot:IWeaveTreeNode = outputCSV.getHierarchyRoot();
						return csvRoot.getChildren().map(function(csvNode:IColumnReference, ..._):IWeaveTreeNode {
							var meta:Object = csvNode.getColumnMetadata();
							meta[SCRIPT_OUTPUT_NAME] = meta[CSVDataSource.METADATA_COLUMN_NAME];
							return generateHierarchyNode(meta);
						});
					}
				});
			}
			return _rootNode;
		}

		override protected function generateHierarchyNode(metadata:Object):IWeaveTreeNode
		{
			if (!metadata)
				return null;

			return new ColumnTreeNode({
				dataSource: this,
				idFields: [SCRIPT_OUTPUT_NAME],
				data: metadata
			});
		}

		override protected function requestColumnFromSource(proxyColumn:ProxyColumn):void
		{
			var name:String = proxyColumn.getMetadata(SCRIPT_OUTPUT_NAME);
			var column:IAttributeColumn = outputCSV.getColumnById(name);
			if (column)
				proxyColumn.setInternalColumn(column);
			else
				proxyColumn.dataUnavailable();
		}
	}
}

revert RDataSource to previous revision

revert RDataSource to previous revision


package krewfw.core_internal {

    import flash.utils.Dictionary;

    import krewfw.utils.krew;
    import krewfw.core.KrewGameObject;

    //------------------------------------------------------------
    public class NotificationService {

        private var _publishers:Dictionary = new Dictionary();
        private var _listenerCount:int = 0;
        private var _messageQueue:Vector.<Object> = new Vector.<Object>();

        //------------------------------------------------------------
        public function NotificationService() {

        }

        public function addListener(listener:KrewGameObject,
                                    eventType:String, callback:Function):void {
            if (!_publishers[eventType]) {
                _publishers[eventType] = new NotificationPublisher(eventType);
                krew.fwlog('+++ create publisher: ' + eventType + ' +++');
            }

            _publishers[eventType].addListener(listener, callback);
        }

        public function removeListener(listener:KrewGameObject, eventType:String):Boolean {
            if (!_publishers[eventType]) {
                krew.fwlog('[Error] Event publisher is absent: ' + eventType);
                return false;
            }

            _publishers[eventType].removeListener(listener);
            if (_publishers[eventType].numListener == 0) {
                delete _publishers[eventType];
                krew.fwlog('--- delete publisher: ' + eventType + ' ---');
            }
            return true;
        }

        public function postMessage(eventType:String, eventArgs:Object):void {
            _messageQueue.push({
                type: eventType,
                args: eventArgs
            });
        }

        public function broadcastMessage():void {
            if (_messageQueue.length == 0) { return; }

            var processingMsgs:Vector.<Object> = _messageQueue.slice(); // copy vector
            _messageQueue = new Vector.<Object>(); // clear vector

            for each (var msg:Object in processingMsgs) {
                var eventType:String = msg.type;
                if (!_publishers[eventType]) { continue; }

                var eventArgs:Object = msg.args;
                var publisher:NotificationPublisher = _publishers[eventType];
                publisher.publish(eventArgs);
            }

            // ToDo: for の中で Message 投げられてたら再帰
        }
    }
}

package krewfw.core_internal {

    import flash.utils.Dictionary;

    import krewfw.utils.krew;
    import krewfw.core.KrewGameObject;

    //------------------------------------------------------------
    public class NotificationService {

        public static var MAX_LOOP_COUNT:int = 8;

        private var _publishers:Dictionary = new Dictionary();
        private var _listenerCount:int = 0;
        private var _messageQueue:Vector.<Object> = new Vector.<Object>();

        //------------------------------------------------------------
        public function NotificationService() {}

        public function addListener(listener:KrewGameObject,
                                    eventType:String, callback:Function):void {
            if (!_publishers[eventType]) {
                _publishers[eventType] = new NotificationPublisher(eventType);
                krew.fwlog('+++ create publisher: ' + eventType + ' +++');
            }

            _publishers[eventType].addListener(listener, callback);
        }

        public function removeListener(listener:KrewGameObject, eventType:String):Boolean {
            if (!_publishers[eventType]) {
                krew.fwlog('[Error] Event publisher is absent: ' + eventType);
                return false;
            }

            _publishers[eventType].removeListener(listener);
            if (_publishers[eventType].numListener == 0) {
                delete _publishers[eventType];
                krew.fwlog('--- delete publisher: ' + eventType + ' ---');
            }
            return true;
        }

        public function postMessage(eventType:String, eventArgs:Object):void {
            _messageQueue.push({
                type: eventType,
                args: eventArgs
            });
        }

        public function broadcastMessage(recallCount:int=0):void {
            if (_messageQueue.length == 0) { return; }

            var processingMsgs:Vector.<Object> = _messageQueue.slice(); // copy vector
            _messageQueue = new Vector.<Object>(); // clear vector

            for each (var msg:Object in processingMsgs) {
                var eventType:String = msg.type;
                if (!_publishers[eventType]) { continue; }

                var eventArgs:Object = msg.args;
                var publisher:NotificationPublisher = _publishers[eventType];
                publisher.publish(eventArgs);
            }

            // イベントのハンドリングの中でさらに Message が投げられていた場合、
            // 再帰して投げられるイベントがなくなるまで処理を継続する。
            // ただし Actor 間でイベントを投げ合うループ構造ができてしまうと無限ループになるため
            // セーフティとして試行回数には制限をかける
            if (_messageQueue.length > 0  &&  recallCount < MAX_LOOP_COUNT) {
                this.broadcastMessage(recallCount + 1);
            } else {
                // 処理しきれなかったイベントは諦める（そうしないとイベントの数が肥大化しうるから）
                // * そもそもここが呼ばれる場合は設計が間違っている。
                //   このログは出力されないべきである
                _messageQueue = new Vector.<Object>();
                krew.fwlog('[Warning!!] Event handling seems to be infinite loop!');
            }
        }
    }
}

Modify NotificationService

Modify NotificationService


package com.benoitfreslon.layoutmanager {

	import flash.display.MovieClip;
	import flash.events.Event;
	import flash.system.Capabilities;
	import flash.utils.getDefinitionByName;
	import starling.display.Button;
	import starling.display.DisplayObject;
	import starling.display.DisplayObjectContainer;
	import starling.display.Image;
	import starling.display.Quad;
	import starling.display.Sprite;
	import starling.text.TextField;
	import starling.textures.Texture;
	import starling.utils.AssetManager;
	import starling.display.ButtonExtended;

	/**
	 * Loader of Layout
	 * @version 1.04
	 * @author Benoît Freslon
	 */
	public class LayoutLoader {
		private var _movieclip : MovieClip;
		private var _displayObject : DisplayObjectContainer;
		private var _rootObject : DisplayObjectContainer;
		private var _assetManager : AssetManager;
		static public var debug : Boolean = false;
		private var onLoad : Function = function() : void {

		};

		/**
		 * Loader of Layout class.
		 */
		public function LayoutLoader() {
			super();
			debug = Capabilities.isDebugger
		}

		/**
		 * Load a layout from a MovieClip added in ActionScript.
		 * Embed a SWC file with all your layouts in your ActionScript 3.0 project and use this lib to load and display your layouts.
		 *
		 * @param	displayObject The starling.display.DisplayObject where the layout should be displayed
		 * @param	LayoutClass	The layout class Embed in the SWC file.
		 * @param	assetManager The AssetManager instance where all assets are loaded.
		 * @param	callBack The callback function when the layout is loaded and displayed.
		 */
		public function loadLayout( rootObject : DisplayObjectContainer , LayoutClass : Class , assetManager : AssetManager , callBack : Function = null ) : void {

			if ( debug )
				trace( "LayoutLoader: loadLayout" , rootObject , LayoutClass , assetManager , callBack );

			_rootObject = rootObject;
			_displayObject = rootObject;
			_assetManager = assetManager;
			_movieclip = new LayoutClass();
			_movieclip.addEventListener( Event.ENTER_FRAME , layoutLoaded );
			if ( onLoad != null )
				onLoad = callBack
		}

		public function loadLayoutIn( rootObject : DisplayObjectContainer , displayObject : DisplayObjectContainer , LayoutClass : Class , assetManager : AssetManager , callBack : Function = null ) : void {
			if ( debug )
				trace( "LayoutLoader: loadLayoutIn" , rootObject , displayObject , LayoutClass , assetManager , callBack );

			_rootObject = rootObject
			_displayObject = displayObject;
			_assetManager = assetManager;
			_movieclip = new LayoutClass();
			_movieclip.addEventListener( Event.ENTER_FRAME , layoutLoaded );
			if ( onLoad != null )
				onLoad = callBack
		}

		private function layoutLoaded( e : Event ) : void {

			if ( debug )
				trace( "LayoutLoader: layoutLoaded" );

			_movieclip.removeEventListener( Event.ENTER_FRAME , layoutLoaded );
			parseMovieClip( _movieclip , _rootObject , _displayObject );
			loaded();
		}

		private function parseMovieClip( mc : MovieClip , root : DisplayObjectContainer , container : DisplayObjectContainer ) : void {
			var child : BFObject;
			var n : int = mc.numChildren;
			for ( var i : uint = 0 ; i < n ; ++i ) {
				child = mc.getChildAt( i ) as BFObject;
				if ( child ) {

					if ( child.mainClass ) {

						var a : Array = [ ButtonExtended ];

						var objectClass : Class;
						if ( child.className ) {
							objectClass = getDefinitionByName( child.className ) as Class;
						} else {
							objectClass = getDefinitionByName( child.mainClass ) as Class;
						}

						var obj : DisplayObject;
						if ( child.mainClass == "starling.display.Image" ) {
							obj = addImage( objectClass , child as BFImage );
						} else if ( child.mainClass == "starling.display.ButtonExtended" ) {
							obj = addButton( objectClass , child as BFButton );
						} else if ( child.mainClass == "starling.text.TextField" ) {
							obj = addTextField( objectClass , child as BFTextField );
						} else if ( child.mainClass == "starling.display.Sprite" ) {
							obj = addSprite( objectClass , child as BFSprite );
						} else {
							throw new Error( "No mainClass defined in '" + child + "'" );
						}

						if ( child.hasOwnProperty( "params" ) ) {
							for ( var metatags : String in child.params ) {
								if ( obj.hasOwnProperty( metatags ) )
									obj[ metatags ] = child.params[ metatags ];
							}
						}
						obj.name = child.name;

						obj.x = int( child.x );
						obj.y = int( child.y );
						//obj.scaleX = child.scaleX;
						//obj.scaleY = child.scaleY;
						if ( child.flipX ) {
							obj.scaleX = -1;
						}
						if ( child.flipY ) {
							obj.scaleY = -1;
						}
						obj.alpha = child.alpha;
						obj.rotation = child.rotation;
						obj.visible = child.isVisible;

						container.addChild( obj );

						if ( obj.hasOwnProperty( "tag" ) ) {
							obj[ "tag" ] = child.tag;
						}
						if ( obj.hasOwnProperty( "userData" ) ) {
							obj[ "userData" ] = child.userData;
						}
						if ( _rootObject.hasOwnProperty( child.name ) ) {
							_rootObject[ child.name ] = obj as objectClass;
						} else if ( child.name.split( "__id" ).length == 1 && child.name.split( "instance" ).length == 1 ) {
							throw new Error( "No public property '" + child.name + "' declared in " + _rootObject );
						}
					} else {

							//trace( new Error( "No className defined " + child ) );
					}
				}
			}
		}

		private function loaded() : void {
			_assetManager = null;
			_displayObject = null;
			_movieclip = null;
			_rootObject = null;
			onLoad();
		}

		private function addImage( objectClass : Class , child : BFImage ) : Image {
			var tex : Texture = getTexture( child , child.texture , child.width , child.height )
			var img : Image = new objectClass( tex ) as Image;
			img.pivotX = int( img.width * child.pivotX );
			img.pivotY = int( img.height * child.pivotY );
			return img;
		}

		private function addSprite( objectClass : Class , child : BFSprite ) : Sprite {
			var s : Sprite = new objectClass() as Sprite;
			parseMovieClip( child as MovieClip , s as DisplayObjectContainer , s as DisplayObjectContainer );
			return s;
		}

		private function addTextField( objectClass : Class , child : BFTextField ) : TextField {
			var t : TextField = new objectClass( child.width , child.height , "" ) as TextField;
			t.autoSize = child.autoSize;
			t.fontName = child.fontName;
			t.fontSize = child.fontSize;
			t.color = child.color;
			t.hAlign = child.hAlign;
			t.vAlign = child.vAlign;
			t.bold = child.bold;
			t.italic = child.italic;
			t.border = child.border;
			t.underline = child.underline;
			t.pivotX = int( t.width * child.pivotX );
			t.pivotY = int( t.height * child.pivotY );
			var text : String = child.text;
			text = text.replace( "\\r" , "\r" );
			text = text.replace( "\\n" , "\n" );
			text = text.replace( "\\t" , "\t" );
			t.text = text;
			t.width = child.width;
			t.height = child.height;
			return t;
		}

		// TODO Parse BFButton and addChild objects inside Button
		private function addButton( objectClass : Class , child : BFButton ) : Button {
			var bt : Button = new objectClass( getTexture( child , child.upState , child.width , child.height ) ) as Button;
			bt.fontBold = child.bold;
			bt.fontColor = child.color;
			bt.fontName = child.fontName;
			bt.fontSize = child.fontSize;
			bt.alphaWhenDisabled = child.alphaWhenDisabled;
			bt.scaleWhenDown = child.scaleWhenDown;
			bt.text = child.text;
			bt.pivotX = int( bt.width * child.pivotX );
			bt.pivotY = int( bt.height * child.pivotY );

			if ( child.downState )
				bt.downState = _assetManager.getTexture( child.downState );

			if ( bt.hasOwnProperty( "overState" ) && child.overState )
				bt[ "overState" ] = _assetManager.getTexture( child.overState );

			if ( bt.hasOwnProperty( "onTouch" ) && _rootObject.hasOwnProperty( child.onTouch ) ) {
				bt[ "onTouch" ] = _rootObject[ child.onTouch ];
			} else if ( bt.hasOwnProperty( "onTouch" ) && child.onTouch != "" && !_rootObject.hasOwnProperty( child.onTouch ) ) {
				throw new Error( "The public method '" + child.onTouch + "' is not defined in " + _rootObject ) ;
			}
			return bt;
		}

		private function getTexture( child : BFObject , textureName : String , w : Number , h : Number ) : Texture {
			if ( textureName == "" ) {
				//trace( new Error( "No texture defined in '" + child + " - name: "+child.name+"' in "+_displayObject+". Default texture used." ) );
				return Texture.empty( w , h );
			} else {
				var tex : Texture = _assetManager.getTexture( textureName );
				if ( tex == null ) {
					trace( new Error( "Texture '" + textureName + "' defined in '" + child + " - name: " + child.name + "' in " + _displayObject + " doesn't exist. Default texture used." ));
					return Texture.empty( w , h );
				}
				return tex;
			}
		}
	}

}

package com.benoitfreslon.layoutmanager {

	import flash.display.MovieClip;
	import flash.events.Event;
	import flash.system.Capabilities;
	import flash.utils.getDefinitionByName;

	import starling.display.Button;
	import starling.display.ButtonExtended;
	import starling.display.DisplayObject;
	import starling.display.DisplayObjectContainer;
	import starling.display.Image;
	import starling.display.Quad;
	import starling.display.Sprite;
	import starling.text.TextField;
	import starling.textures.Texture;
	import starling.utils.AssetManager;

	/**
	 * Loader of Layout
	 * @version 1.04
	 * @author Benoît Freslon
	 */
	public class LayoutLoader {
		private var _movieclip : MovieClip;
		private var _displayObject : DisplayObjectContainer;
		private var _rootObject : DisplayObjectContainer;
		private var _assetManager : AssetManager;
		static public var debug : Boolean = false;
		private var onLoad : Function = function() : void {

		};

		/**
		 * Loader of Layout class.
		 */
		public function LayoutLoader() {
			super();
			debug = Capabilities.isDebugger
		}

		/**
		 * Load a layout from a MovieClip added in ActionScript.
		 * Embed a SWC file with all your layouts in your ActionScript 3.0 project and use this lib to load and display your layouts.
		 *
		 * @param	displayObject The starling.display.DisplayObject where the layout should be displayed
		 * @param	LayoutClass	The layout class Embed in the SWC file.
		 * @param	assetManager The AssetManager instance where all assets are loaded.
		 * @param	callBack The callback function when the layout is loaded and displayed.
		 */
		public function loadLayout( rootObject : DisplayObjectContainer , LayoutClass : Class , assetManager : AssetManager , callBack : Function = null ) : void {

			if ( debug )
				trace( "LayoutLoader: loadLayout" , rootObject , LayoutClass , assetManager , callBack );

			_rootObject = rootObject;
			_displayObject = rootObject;
			_assetManager = assetManager;
			_movieclip = new LayoutClass();
			_movieclip.addEventListener( Event.ENTER_FRAME , layoutLoaded );
			if ( onLoad != null )
				onLoad = callBack
		}

		public function loadLayoutIn( rootObject : DisplayObjectContainer , displayObject : DisplayObjectContainer , LayoutClass : Class , assetManager : AssetManager , callBack : Function = null ) : void {
			if ( debug )
				trace( "LayoutLoader: loadLayoutIn" , rootObject , displayObject , LayoutClass , assetManager , callBack );

			_rootObject = rootObject
			_displayObject = displayObject;
			_assetManager = assetManager;
			_movieclip = new LayoutClass();
			_movieclip.addEventListener( Event.ENTER_FRAME , layoutLoaded );
			if ( onLoad != null )
				onLoad = callBack
		}

		private function layoutLoaded( e : Event ) : void {

			if ( debug )
				trace( "LayoutLoader: layoutLoaded" );

			_movieclip.removeEventListener( Event.ENTER_FRAME , layoutLoaded );
			parseMovieClip( _movieclip , _rootObject , _displayObject );
			loaded();
		}

		private function parseMovieClip( mc : MovieClip , root : DisplayObjectContainer , container : DisplayObjectContainer ) : void {
			var child : BFObject;
			var n : int = mc.numChildren;
			for ( var i : uint = 0 ; i < n ; ++i ) {
				child = mc.getChildAt( i ) as BFObject;
				if ( child ) {

					if ( child.mainClass ) {

						var a : Array = [ ButtonExtended ];

						var objectClass : Class;
						if (debug) trace(child.className);
						if ( child.className ) {
							objectClass = getDefinitionByName( child.className ) as Class;
						} else {
							objectClass = getDefinitionByName( child.mainClass ) as Class;
						}

						var obj : DisplayObject;
						if ( child.mainClass == "starling.display.Image" ) {
							obj = addImage( objectClass , child as BFImage );
						} else if ( child.mainClass == "starling.display.ButtonExtended" ) {
							obj = addButton( objectClass , child as BFButton );
						} else if ( child.mainClass == "starling.text.TextField" ) {
							obj = addTextField( objectClass , child as BFTextField );
						} else if ( child.mainClass == "starling.display.Sprite" ) {
							obj = addSprite( objectClass , child as BFSprite );
						} else {
							throw new Error( "No mainClass defined in '" + child + "'" );
						}

						if ( child.hasOwnProperty( "params" ) ) {
							for ( var metatags : String in child.params ) {
								if ( obj.hasOwnProperty( metatags ) )
									obj[ metatags ] = child.params[ metatags ];
							}
						}
						obj.name = child.name;

						obj.x = int( child.x );
						obj.y = int( child.y );
						//obj.scaleX = child.scaleX;
						//obj.scaleY = child.scaleY;
						if ( child.flipX ) {
							obj.scaleX = -1;
						}
						if ( child.flipY ) {
							obj.scaleY = -1;
						}
						obj.alpha = child.alpha;
						obj.rotation = child.rotation;
						obj.visible = child.isVisible;

						container.addChild( obj );

						if ( obj.hasOwnProperty( "tag" ) ) {
							obj[ "tag" ] = child.tag;
						}
						if ( obj.hasOwnProperty( "userData" ) ) {
							obj[ "userData" ] = child.userData;
						}
						if ( _rootObject.hasOwnProperty( child.name ) ) {
							_rootObject[ child.name ] = obj as objectClass;
						} else if ( child.name.split( "__id" ).length == 1 && child.name.split( "instance" ).length == 1 ) {
							throw new Error( "No public property '" + child.name + "' declared in " + _rootObject );
						}
					} else {

							//trace( new Error( "No className defined " + child ) );
					}
				}
			}
		}

		private function loaded() : void {
			_assetManager = null;
			_displayObject = null;
			_movieclip = null;
			_rootObject = null;
			onLoad();
		}

		private function addImage( objectClass : Class , child : BFImage ) : Image {
			var tex : Texture = getTexture( child , child.texture , child.width , child.height )
			var img : Image = new objectClass( tex ) as Image;
			img.pivotX = int( img.width * child.pivotX );
			img.pivotY = int( img.height * child.pivotY );
			return img;
		}

		private function addSprite( objectClass : Class , child : BFSprite ) : Sprite {
			var s : Sprite = new objectClass() as Sprite;
			parseMovieClip( child as MovieClip , s as DisplayObjectContainer , s as DisplayObjectContainer );
			return s;
		}

		private function addTextField( objectClass : Class , child : BFTextField ) : TextField {
			var t : TextField = new objectClass( child.width , child.height , "" ) as TextField;
			t.autoSize = child.autoSize;
			t.fontName = child.fontName;
			t.fontSize = child.fontSize;
			t.color = child.color;
			t.hAlign = child.hAlign;
			t.vAlign = child.vAlign;
			t.bold = child.bold;
			t.italic = child.italic;
			t.border = child.border;
			t.underline = child.underline;
			t.pivotX = int( t.width * child.pivotX );
			t.pivotY = int( t.height * child.pivotY );
			var text : String = child.text;
			text = text.replace( "\\r" , "\r" );
			text = text.replace( "\\n" , "\n" );
			text = text.replace( "\\t" , "\t" );
			t.text = text;
			t.width = child.width;
			t.height = child.height;
			return t;
		}

		// TODO Parse BFButton and addChild objects inside Button
		private function addButton( objectClass : Class , child : BFButton ) : Button {
			var bt : Button = new objectClass( getTexture( child , child.upState , child.width , child.height ) ) as Button;
			bt.fontBold = child.bold;
			bt.fontColor = child.color;
			bt.fontName = child.fontName;
			bt.fontSize = child.fontSize;
			bt.alphaWhenDisabled = child.alphaWhenDisabled;
			bt.scaleWhenDown = child.scaleWhenDown;
			bt.text = child.text;
			bt.pivotX = int( bt.width * child.pivotX );
			bt.pivotY = int( bt.height * child.pivotY );

			if ( child.downState )
				bt.downState = _assetManager.getTexture( child.downState );

			if ( bt.hasOwnProperty( "overState" ) && child.overState )
				bt[ "overState" ] = _assetManager.getTexture( child.overState );

			if ( bt.hasOwnProperty( "onTouch" ) && _rootObject.hasOwnProperty( child.onTouch ) ) {
				bt[ "onTouch" ] = _rootObject[ child.onTouch ];
			} else if ( bt.hasOwnProperty( "onTouch" ) && child.onTouch != "" && !_rootObject.hasOwnProperty( child.onTouch ) ) {
				throw new Error( "The public method '" + child.onTouch + "' is not defined in " + _rootObject ) ;
			}
			return bt;
		}

		private function getTexture( child : BFObject , textureName : String , w : Number , h : Number ) : Texture {
			if ( textureName == "" ) {
				//trace( new Error( "No texture defined in '" + child + " - name: "+child.name+"' in "+_displayObject+". Default texture used." ) );
				return Texture.empty( w , h );
			} else {
				var tex : Texture = _assetManager.getTexture( textureName );
				if ( tex == null ) {
					trace( new Error( "Texture '" + textureName + "' defined in '" + child + " - name: " + child.name + "' in " + _displayObject + " doesn't exist. Default texture used." ));
					return Texture.empty( w , h );
				}
				return tex;
			}
		}
	}

}

Debug message

Debug message


package org.apache.flex.core
{
    COMPILE::JS
    {
        import org.apache.flex.events.BrowserEvent;
        import org.apache.flex.events.EventDispatcher;
		import goog.events;
    }

    [ExcludeClass]
    COMPILE::AS3
    public class HTMLElementWrapper {}

	COMPILE::JS
	public class HTMLElementWrapper extends EventDispatcher implements IStrand
	{

		//--------------------------------------
		//   Static Property
		//--------------------------------------

		static public var googFireListener:Function;

        /**
         * The properties that triggers the static initializer
         */
		static public var installedOverride:Boolean = installOverride();

		//--------------------------------------
		//   Static Function
		//--------------------------------------

        /**
         * @param listener The listener object to call {goog.events.Listener}.
         * @param eventObject The event object to pass to the listener.
         * @return Result of listener.
         */
		static public function fireListenerOverride(listener:Object, eventObject:BrowserEvent):Boolean
		{
			var e:BrowserEvent = new BrowserEvent();
			e.wrappedEvent = eventObject.wrappedEvent;
			return HTMLElementWrapper.googFireListener(listener, e);
		}

        /**
         * Static initializer
         */
		static public function installOverride():Boolean
		{
			HTMLElementWrapper.googFireListener = events.fireListener;
			events.fireListener = HTMLElementWrapper.fireListenerOverride;
			return true;
		}

		//--------------------------------------
		//   Property
		//--------------------------------------

		public var element:EventTarget;
		public var model:IBead;

		protected var beads:Array;
		protected var internalDisplay:String = 'inline';

		//--------------------------------------
		//   Function
		//--------------------------------------

		public function get MXMLDescriptor():Array
		{
			return null;
		}

        /**
         * @param bead The new bead.
         */
		public function addBead(bead:IBead):void
		{
			if (!beads)
			{
				beads = [];
			}

			beads.push(bead);

			if (bead is IBeadModel)
			{
				model = bead;
			}

			bead.strand = this;
		}

        /**
         * @param classOrInterface The requested bead type.
         * @return The bead.
         */
		public function getBeadByType(classOrInterface:Class):IBead
		{
			var bead:IBead, i:uint, n:uint;

			n = beads.length;

			for (i = 0; i < n; i++)
			{
				bead = beads[i];

				if (bead is classOrInterface)
				{
					return bead;
				}
			}

			return null;
		}

		/**
		 * @param bead The bead to remove.
		 * @return The bead.
		 */
		public function removeBead(bead:IBead):IBead
		{
			var i:uint, n:uint, value:Object;

			n = beads.length;

			for (i = 0; i < n; i++)
			{
				value = beads[i];

				if (bead === value)
				{
					beads.splice(i, 1);

					return bead;
				}
			}

			return null;
		}
	}
}

package org.apache.flex.core
{
    COMPILE::JS
    {
        import org.apache.flex.events.BrowserEvent;
        import org.apache.flex.events.EventDispatcher;
        import goog.events;
        import goog.events.EventTarget;
    }

	COMPILE::JS
	public class HTMLElementWrapper extends EventDispatcher implements IStrand
	{

		//--------------------------------------
		//   Static Property
		//--------------------------------------

		static public var googFireListener:Function;

        /**
         * The properties that triggers the static initializer
         */
		static public var installedOverride:Boolean = installOverride();

		//--------------------------------------
		//   Static Function
		//--------------------------------------

        /**
         * @param listener The listener object to call {goog.events.Listener}.
         * @param eventObject The event object to pass to the listener.
         * @return Result of listener.
         */
		static public function fireListenerOverride(listener:Object, eventObject:BrowserEvent):Boolean
		{
			var e:BrowserEvent = new BrowserEvent();
			e.wrappedEvent = eventObject.wrappedEvent;
			return HTMLElementWrapper.googFireListener(listener, e);
		}

        /**
         * Static initializer
         */
		static public function installOverride():Boolean
		{
			HTMLElementWrapper.googFireListener = goog.events.fireListener;
			goog.events.fireListener = HTMLElementWrapper.fireListenerOverride;
			return true;
		}

		//--------------------------------------
		//   Property
		//--------------------------------------

		public var element:EventTarget;
		public var model:IBead;

		protected var beads:Array;
		protected var internalDisplay:String = 'inline';

		//--------------------------------------
		//   Function
		//--------------------------------------

		public function get MXMLDescriptor():Array
		{
			return null;
		}

        /**
         * @param bead The new bead.
         */
		public function addBead(bead:IBead):void
		{
			if (!beads)
			{
				beads = [];
			}

			beads.push(bead);

			if (bead is IBeadModel)
			{
				model = bead;
			}

			bead.strand = this;
		}

        /**
         * @param classOrInterface The requested bead type.
         * @return The bead.
         */
		public function getBeadByType(classOrInterface:Class):IBead
		{
			var bead:IBead, i:uint, n:uint;

			n = beads.length;

			for (i = 0; i < n; i++)
			{
				bead = beads[i];

				if (bead is classOrInterface)
				{
					return bead;
				}
			}

			return null;
		}

		/**
		 * @param bead The bead to remove.
		 * @return The bead.
		 */
		public function removeBead(bead:IBead):IBead
		{
			var i:uint, n:uint, value:Object;

			n = beads.length;

			for (i = 0; i < n; i++)
			{
				value = beads[i];

				if (bead === value)
				{
					beads.splice(i, 1);

					return bead;
				}
			}

			return null;
		}
	}
}

tweak HTMLElementWrapper to use a goog.events class


/* ***** 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.source
{
	import weavejs.WeaveAPI;
	import weavejs.api.data.ColumnMetadata;
	import weavejs.api.data.DataType;
	import weavejs.api.data.IDataSource;
	import weavejs.api.data.IDataSource_File;
	import weavejs.api.data.IWeaveTreeNode;
	import weavejs.core.LinkableFile;
	import weavejs.core.LinkableString;
	import weavejs.data.column.GeometryColumn;
	import weavejs.data.column.NumberColumn;
	import weavejs.data.column.ProxyColumn;
	import weavejs.data.column.StringColumn;
	import weavejs.geom.GeneralizedGeometry;
	import weavejs.geom.GeoJSON;
	import weavejs.net.ResponseType;
	import weavejs.util.ArrayUtils;
	import weavejs.util.JS;

	public class GeoJSONDataSource extends AbstractDataSource implements IDataSource_File
	{
		WeaveAPI.ClassRegistry.registerImplementation(IDataSource, GeoJSONDataSource, "GeoJSON file");

		public function GeoJSONDataSource()
		{
		}

		public const url:LinkableFile = Weave.linkableChild(this, new LinkableFile(null, null, ResponseType.JSON), handleFile);
		public const keyType:LinkableString = Weave.linkableChild(this, LinkableString);
		public const keyProperty:LinkableString = Weave.linkableChild(this, LinkableString);

		/**
		 * Overrides the projection specified in the GeoJSON object.
		 */
		public const projection:LinkableString = Weave.linkableChild(this, LinkableString);

		/**
		 * The GeoJSON data.
		 */
		private var jsonData:GeoJSONDataSourceData = null;

		/**
		 * Gets the projection metadata used in the geometry column.
		 */
		public function getProjection():String
		{
			return projection.value
				|| (jsonData ? jsonData.projection : null)
				|| "EPSG:4326";
		}
		/**
		 * Gets the keyType metadata used in the columns.
		 */
		public function getKeyType():String
		{
			var kt:String = keyType.value;
			if (!kt)
			{
				kt = url.value;
				if (keyProperty.value)
					kt += "#" + keyProperty.value;
			}
			return kt;
		}

		override protected function get initializationComplete():Boolean
		{
			return super.initializationComplete && !Weave.isBusy(url) && jsonData;
		}

		/**
		 * This gets called as a grouped callback.
		 */
		override protected function initialize(forceRefresh:Boolean = false):void
		{
			_rootNode = null;

			if (Weave.detectChange(initialize, keyType, keyProperty))
			{
				if (jsonData)
					jsonData.resetQKeys(getKeyType(), keyProperty.value);
			}

			// recalculate all columns previously requested because data may have changed.
			super.initialize(true);
		}

		private function handleFile():void
		{
			if (Weave.isBusy(url))
				return;

			jsonData = null;

			if (!url.result)
			{
				hierarchyRefresh.triggerCallbacks();

				if (url.error)
					JS.error(url.error);

				return;
			}

			try
			{
				var obj:Object = url.result;

				// make sure it's valid GeoJSON
				if (!GeoJSON.isGeoJSONObject(obj))
					throw new Error("Invalid GeoJSON file: " + url.value);

				// parse data
				jsonData = new GeoJSONDataSourceData(obj, getKeyType(), keyProperty.value);

				hierarchyRefresh.triggerCallbacks();
			}
			catch (e:Error)
			{
				JS.error(e);
			}
		}

		/**
		 * Gets the root node of the attribute hierarchy.
		 */
		override public function getHierarchyRoot():IWeaveTreeNode
		{
			if (!(_rootNode is GeoJSONDataSourceNode))
			{
				var meta:Object = {};
				meta[ColumnMetadata.TITLE] = Weave.getRoot(this).getName(this);

				var rootChildren:Array = [];
				if (jsonData)
				{
					// include empty string for the geometry column
					rootChildren = [''].concat(jsonData.propertyNames)
						.map(function(n:String, i:*, a:*):*{ return generateHierarchyNode(n); })
						.filter(function(n:Object, ..._):Boolean{ return n != null; });
				}

				_rootNode = new GeoJSONDataSourceNode(this, meta, rootChildren);
			}
			return _rootNode;
		}

		override protected function generateHierarchyNode(metadata:Object):IWeaveTreeNode
		{
			if (metadata == null || !jsonData)
				return null;

			if (metadata is String)
			{
				var str:String = metadata as String;
				metadata = {};
				metadata[GEOJSON_PROPERTY_NAME] = str;
			}
			if (metadata && metadata.hasOwnProperty(GEOJSON_PROPERTY_NAME))
			{
				metadata = getMetadataForProperty(metadata[GEOJSON_PROPERTY_NAME]);
				return new GeoJSONDataSourceNode(this, metadata, null, [GEOJSON_PROPERTY_NAME]);
			}

			return null;
		}

		override protected function requestColumnFromSource(proxyColumn:ProxyColumn):void
		{
			var propertyName:String = proxyColumn.getMetadata(GEOJSON_PROPERTY_NAME);
			var metadata:Object = getMetadataForProperty(propertyName);
			if (!metadata || !jsonData || (propertyName && jsonData.propertyNames.indexOf(propertyName) < 0))
			{
				proxyColumn.dataUnavailable();
				return;
			}
			proxyColumn.setMetadata(metadata);

			var dataType:String = metadata[ColumnMetadata.DATA_TYPE];
			if (dataType == DataType.GEOMETRY)
			{
				var qkeys:Array = [];
				var geoms:Array = [];
				var i:int = 0;
				var initGeoms:Function = function(stopTime:int):Number
				{
					if (!jsonData)
					{
						proxyColumn.dataUnavailable();
						return 1;
					}
					for (; i < jsonData.qkeys.length; i++)
					{
						if (JS.now() > stopTime)
							return i / jsonData.qkeys.length;

						var geomsFromJson:Array = GeneralizedGeometry.fromGeoJson(jsonData.geometries[i]);
						for each (var geom:GeneralizedGeometry in geomsFromJson)
						{
							qkeys.push(jsonData.qkeys[i]);
							geoms.push(geom);
						}
					}
					return 1;
				}
				var setGeoms:Function = function():void
				{
					var gc:GeometryColumn = new GeometryColumn(metadata);
					gc.setRecords(qkeys, geoms);
					proxyColumn.setInternalColumn(gc);
				}
				// high priority because not much can be done without data
				WeaveAPI.Scheduler.startTask(proxyColumn, initGeoms, WeaveAPI.TASK_PRIORITY_HIGH, setGeoms);
			}
			else
			{
				var data:Array = ArrayUtils.pluck(jsonData.properties, propertyName);
				var type:String = jsonData.propertyTypes[propertyName];
				if (type == 'number')
				{
					var nc:NumberColumn = new NumberColumn(metadata);
					nc.setRecords(jsonData.qkeys, data);
					proxyColumn.setInternalColumn(nc);
				}
				else
				{
					var sc:StringColumn = new StringColumn(metadata);
					sc.setRecords(jsonData.qkeys, data);
					proxyColumn.setInternalColumn(sc);
				}
			}
		}

		private function getMetadataForProperty(propertyName:String):Object
		{
			if (!jsonData)
				return null;

			var meta:Object = null;
			if (!propertyName)
			{
				meta = {};
				meta[GEOJSON_PROPERTY_NAME] = '';
				meta[ColumnMetadata.TITLE] = getGeomColumnTitle();
				meta[ColumnMetadata.KEY_TYPE] = getKeyType();
				meta[ColumnMetadata.DATA_TYPE] = DataType.GEOMETRY;
				meta[ColumnMetadata.PROJECTION] = getProjection();
			}
			else if (jsonData.propertyNames.indexOf(propertyName) >= 0)
			{
				meta = {};
				meta[GEOJSON_PROPERTY_NAME] = propertyName;
				meta[ColumnMetadata.TITLE] = propertyName;
				meta[ColumnMetadata.KEY_TYPE] = getKeyType();

				if (propertyName == keyProperty.value)
					meta[ColumnMetadata.DATA_TYPE] = getKeyType();
				else
					meta[ColumnMetadata.DATA_TYPE] = jsonData.propertyTypes[propertyName];
			}
			return meta;
		}
		private static const GEOJSON_PROPERTY_NAME:String = 'geoJsonPropertyName';

		private function getGeomColumnTitle():String
		{
			return Weave.lang("{0} geometry", Weave.getRoot(this).getName(this));
		}
	}
}

/* ***** 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.source
{
	import weavejs.WeaveAPI;
	import weavejs.api.data.ColumnMetadata;
	import weavejs.api.data.DataType;
	import weavejs.api.data.IDataSource;
	import weavejs.api.data.IDataSource_File;
	import weavejs.api.data.IWeaveTreeNode;
	import weavejs.core.LinkableFile;
	import weavejs.core.LinkableString;
	import weavejs.data.column.GeometryColumn;
	import weavejs.data.column.NumberColumn;
	import weavejs.data.column.ProxyColumn;
	import weavejs.data.column.StringColumn;
	import weavejs.geom.GeneralizedGeometry;
	import weavejs.geom.GeoJSON;
	import weavejs.net.ResponseType;
	import weavejs.util.ArrayUtils;
	import weavejs.util.JS;

	public class GeoJSONDataSource extends AbstractDataSource implements IDataSource_File
	{
		WeaveAPI.ClassRegistry.registerImplementation(IDataSource, GeoJSONDataSource, "GeoJSON file");

		public function GeoJSONDataSource()
		{
		}

		public const url:LinkableFile = Weave.linkableChild(this, new LinkableFile(null, null, ResponseType.JSON), handleFile);
		public const keyType:LinkableString = Weave.linkableChild(this, LinkableString);
		public const keyProperty:LinkableString = Weave.linkableChild(this, LinkableString);

		/**
		 * Overrides the projection specified in the GeoJSON object.
		 */
		public const projection:LinkableString = Weave.linkableChild(this, LinkableString);

		/**
		 * The GeoJSON data.
		 */
		private var jsonData:GeoJSONDataSourceData = null;

		/**
		 * Gets the projection metadata used in the geometry column.
		 */
		public function getProjection():String
		{
			return projection.value
				|| (jsonData ? jsonData.projection : null)
				|| "EPSG:4326";
		}
		/**
		 * Gets the keyType metadata used in the columns.
		 */

		public function getPropertyNames():Array/*Array<string>*/
		{
			return (jsonData && jsonData.propertyNames) ? [].concat(jsonData.propertyNames) : [];
		}
		public function getKeyType():String
		{
			var kt:String = keyType.value;
			if (!kt)
			{
				kt = url.value;
				if (keyProperty.value)
					kt += "#" + keyProperty.value;
			}
			return kt;
		}

		override protected function get initializationComplete():Boolean
		{
			return super.initializationComplete && !Weave.isBusy(url) && jsonData;
		}

		/**
		 * This gets called as a grouped callback.
		 */
		override protected function initialize(forceRefresh:Boolean = false):void
		{
			_rootNode = null;

			if (Weave.detectChange(initialize, keyType, keyProperty))
			{
				if (jsonData)
					jsonData.resetQKeys(getKeyType(), keyProperty.value);
			}

			// recalculate all columns previously requested because data may have changed.
			super.initialize(true);
		}

		private function handleFile():void
		{
			if (Weave.isBusy(url))
				return;

			jsonData = null;

			if (!url.result)
			{
				hierarchyRefresh.triggerCallbacks();

				if (url.error)
					JS.error(url.error);

				return;
			}

			try
			{
				var obj:Object = url.result;

				// make sure it's valid GeoJSON
				if (!GeoJSON.isGeoJSONObject(obj))
					throw new Error("Invalid GeoJSON file: " + url.value);

				// parse data
				jsonData = new GeoJSONDataSourceData(obj, getKeyType(), keyProperty.value);

				hierarchyRefresh.triggerCallbacks();
			}
			catch (e:Error)
			{
				JS.error(e);
			}
		}

		/**
		 * Gets the root node of the attribute hierarchy.
		 */
		override public function getHierarchyRoot():IWeaveTreeNode
		{
			if (!(_rootNode is GeoJSONDataSourceNode))
			{
				var meta:Object = {};
				meta[ColumnMetadata.TITLE] = Weave.getRoot(this).getName(this);

				var rootChildren:Array = [];
				if (jsonData)
				{
					// include empty string for the geometry column
					rootChildren = [''].concat(jsonData.propertyNames)
						.map(function(n:String, i:*, a:*):*{ return generateHierarchyNode(n); })
						.filter(function(n:Object, ..._):Boolean{ return n != null; });
				}

				_rootNode = new GeoJSONDataSourceNode(this, meta, rootChildren);
			}
			return _rootNode;
		}

		override protected function generateHierarchyNode(metadata:Object):IWeaveTreeNode
		{
			if (metadata == null || !jsonData)
				return null;

			if (metadata is String)
			{
				var str:String = metadata as String;
				metadata = {};
				metadata[GEOJSON_PROPERTY_NAME] = str;
			}
			if (metadata && metadata.hasOwnProperty(GEOJSON_PROPERTY_NAME))
			{
				metadata = getMetadataForProperty(metadata[GEOJSON_PROPERTY_NAME]);
				return new GeoJSONDataSourceNode(this, metadata, null, [GEOJSON_PROPERTY_NAME]);
			}

			return null;
		}

		override protected function requestColumnFromSource(proxyColumn:ProxyColumn):void
		{
			var propertyName:String = proxyColumn.getMetadata(GEOJSON_PROPERTY_NAME);
			var metadata:Object = getMetadataForProperty(propertyName);
			if (!metadata || !jsonData || (propertyName && jsonData.propertyNames.indexOf(propertyName) < 0))
			{
				proxyColumn.dataUnavailable();
				return;
			}
			proxyColumn.setMetadata(metadata);

			var dataType:String = metadata[ColumnMetadata.DATA_TYPE];
			if (dataType == DataType.GEOMETRY)
			{
				var qkeys:Array = [];
				var geoms:Array = [];
				var i:int = 0;
				var initGeoms:Function = function(stopTime:int):Number
				{
					if (!jsonData)
					{
						proxyColumn.dataUnavailable();
						return 1;
					}
					for (; i < jsonData.qkeys.length; i++)
					{
						if (JS.now() > stopTime)
							return i / jsonData.qkeys.length;

						var geomsFromJson:Array = GeneralizedGeometry.fromGeoJson(jsonData.geometries[i]);
						for each (var geom:GeneralizedGeometry in geomsFromJson)
						{
							qkeys.push(jsonData.qkeys[i]);
							geoms.push(geom);
						}
					}
					return 1;
				}
				var setGeoms:Function = function():void
				{
					var gc:GeometryColumn = new GeometryColumn(metadata);
					gc.setRecords(qkeys, geoms);
					proxyColumn.setInternalColumn(gc);
				}
				// high priority because not much can be done without data
				WeaveAPI.Scheduler.startTask(proxyColumn, initGeoms, WeaveAPI.TASK_PRIORITY_HIGH, setGeoms);
			}
			else
			{
				var data:Array = ArrayUtils.pluck(jsonData.properties, propertyName);
				var type:String = jsonData.propertyTypes[propertyName];
				if (type == 'number')
				{
					var nc:NumberColumn = new NumberColumn(metadata);
					nc.setRecords(jsonData.qkeys, data);
					proxyColumn.setInternalColumn(nc);
				}
				else
				{
					var sc:StringColumn = new StringColumn(metadata);
					sc.setRecords(jsonData.qkeys, data);
					proxyColumn.setInternalColumn(sc);
				}
			}
		}

		private function getMetadataForProperty(propertyName:String):Object
		{
			if (!jsonData)
				return null;

			var meta:Object = null;
			if (!propertyName)
			{
				meta = {};
				meta[GEOJSON_PROPERTY_NAME] = '';
				meta[ColumnMetadata.TITLE] = getGeomColumnTitle();
				meta[ColumnMetadata.KEY_TYPE] = getKeyType();
				meta[ColumnMetadata.DATA_TYPE] = DataType.GEOMETRY;
				meta[ColumnMetadata.PROJECTION] = getProjection();
			}
			else if (jsonData.propertyNames.indexOf(propertyName) >= 0)
			{
				meta = {};
				meta[GEOJSON_PROPERTY_NAME] = propertyName;
				meta[ColumnMetadata.TITLE] = propertyName;
				meta[ColumnMetadata.KEY_TYPE] = getKeyType();

				if (propertyName == keyProperty.value)
					meta[ColumnMetadata.DATA_TYPE] = getKeyType();
				else
					meta[ColumnMetadata.DATA_TYPE] = jsonData.propertyTypes[propertyName];
			}
			return meta;
		}
		private static const GEOJSON_PROPERTY_NAME:String = 'geoJsonPropertyName';

		private function getGeomColumnTitle():String
		{
			return Weave.lang("{0} geometry", Weave.getRoot(this).getName(this));
		}
	}
}

Add getPropertyNames to GeoJSONDataSource


package dolly {
import dolly.core.dolly_internal;
import dolly.data.ClassLevelCloneable;
import dolly.data.ClassLevelCopyableCloneable;
import dolly.data.PropertyLevelCloneable;
import dolly.data.PropertyLevelCopyableCloneable;

import org.as3commons.reflect.Field;
import org.as3commons.reflect.Type;
import org.flexunit.asserts.assertEquals;
import org.flexunit.asserts.assertNotNull;
import org.flexunit.asserts.assertNull;

use namespace dolly_internal;

public class ClonerTest {

	private var classLevelCloneable:ClassLevelCloneable;
	private var classLevelCloneableType:Type;

	private var classLevelCopyableCloneable:ClassLevelCopyableCloneable;
	private var classLevelCopyableCloneableType:Type;

	private var propertyLevelCloneable:PropertyLevelCloneable;
	private var propertyLevelCloneableType:Type;

	private var propertyLevelCopyableCloneable:PropertyLevelCopyableCloneable;
	private var propertyLevelCopyableCloneableType:Type;

	[Before]
	public function before():void {
		classLevelCloneable = new ClassLevelCloneable();
		classLevelCloneable.property1 = "value 1";
		classLevelCloneable.property2 = "value 2";
		classLevelCloneable.property3 = "value 3";
		classLevelCloneable.writableField = "value 4";
		classLevelCloneableType = Type.forInstance(classLevelCloneable);

		classLevelCopyableCloneable = new ClassLevelCopyableCloneable();
		classLevelCopyableCloneable.property1 = "value 1";
		classLevelCopyableCloneable.property2 = "value 2";
		classLevelCopyableCloneable.property3 = "value 3";
		classLevelCopyableCloneable.writableField = "value 4";
		classLevelCopyableCloneableType = Type.forInstance(classLevelCopyableCloneable);

		propertyLevelCloneable = new PropertyLevelCloneable();
		propertyLevelCloneable.property1 = "value 1";
		propertyLevelCloneable.property2 = "value 2";
		propertyLevelCloneable.property3 = "value 3";
		propertyLevelCloneable.writableField = "value 4";
		propertyLevelCloneableType = Type.forInstance(propertyLevelCloneable);

		propertyLevelCopyableCloneable = new PropertyLevelCopyableCloneable();
		propertyLevelCopyableCloneable.property1 = "value 1";
		propertyLevelCopyableCloneable.property2 = "value 2";
		propertyLevelCopyableCloneable.property3 = "value 3";
		propertyLevelCopyableCloneable.writableField = "value 4";
		propertyLevelCopyableCloneableType = Type.forInstance(propertyLevelCopyableCloneable);
	}

	[After]
	public function after():void {
		classLevelCloneable = null;
		classLevelCloneableType = null;

		classLevelCopyableCloneable = null;
		classLevelCopyableCloneableType = null;

		propertyLevelCloneable = null;
		propertyLevelCloneableType = null;

		propertyLevelCopyableCloneable = null;
		propertyLevelCopyableCloneableType = null;
	}

	[Test]
	public function testGetCloneableFieldsForTypeClassLevelCloneable():void {
		const cloneableFields:Vector.<Field> = Cloner.getCloneableFieldsForType(classLevelCloneableType);
		assertNotNull(cloneableFields);
		assertEquals(4, cloneableFields.length);
	}

	[Test]
	public function testGetCloneableFieldsForTypeClassLevelCopyableCloneable():void {
		const cloneableFields:Vector.<Field> = Cloner.getCloneableFieldsForType(classLevelCopyableCloneableType);
		assertNotNull(cloneableFields);
		assertEquals(4, cloneableFields.length);
	}

	[Test]
	public function testGetCloneableFieldsForTypePropertyLevelCloneable():void {
		const cloneableFields:Vector.<Field> = Cloner.getCloneableFieldsForType(propertyLevelCloneableType);
		assertNotNull(cloneableFields);
		assertEquals(3, cloneableFields.length);
	}

	[Test]
	public function testGetCloneableFieldsForTypePropertyLevelCopyableCloneable():void {
		const cloneableFields:Vector.<Field> = Cloner.getCloneableFieldsForType(propertyLevelCopyableCloneableType);
		assertNotNull(cloneableFields);
		assertEquals(4, cloneableFields.length);
	}

	[Test]
	public function testWithClassLevelCloneable():void {
		const clone1:ClassLevelCloneable = Cloner.clone(classLevelCloneable);
		assertNotNull(clone1);
		assertNotNull(clone1.property1);
		assertEquals(clone1.property1, classLevelCloneable.property1);
		assertNotNull(clone1.property2);
		assertEquals(clone1.property2, classLevelCloneable.property2);
		assertNotNull(clone1.property3);
		assertEquals(clone1.property3, classLevelCloneable.property3);
		assertNotNull(clone1.writableField);
		assertEquals(clone1.writableField, classLevelCloneable.writableField);

		const clone2:ClassLevelCopyableCloneable = Cloner.clone(classLevelCopyableCloneable);
		assertNotNull(clone2);
		assertNotNull(clone2.property1);
		assertEquals(clone2.property1, classLevelCloneable.property1);
		assertNotNull(clone2.property2);
		assertEquals(clone2.property2, classLevelCloneable.property2);
		assertNotNull(clone2.property3);
		assertEquals(clone2.property3, classLevelCloneable.property3);
		assertNotNull(clone2.writableField);
		assertEquals(clone2.writableField, classLevelCloneable.writableField);
	}

	[Test]
	public function testCloneWithPropertyLevelMetadata():void {
		const clone1:PropertyLevelCloneable = Cloner.clone(propertyLevelCloneable);
		assertNotNull(clone1);
		assertNull(clone1.property1);
		assertNotNull(clone1.property2);
		assertEquals(clone1.property2, propertyLevelCloneable.property2);
		assertNotNull(clone1.property3);
		assertEquals(clone1.property3, propertyLevelCloneable.property3);
		assertNotNull(clone1.writableField);
		assertEquals(clone1.writableField, propertyLevelCloneable.writableField);

		const clone2:PropertyLevelCopyableCloneable = Cloner.clone(propertyLevelCopyableCloneable);
		assertNotNull(clone2);
		assertNotNull(clone2.property1);
		assertEquals(clone2.property1, propertyLevelCloneable.property1);
		assertNotNull(clone2.property2);
		assertEquals(clone2.property2, propertyLevelCloneable.property2);
		assertNotNull(clone2.property3);
		assertEquals(clone2.property3, propertyLevelCloneable.property3);
		assertNotNull(clone2.writableField);
		assertEquals(clone2.writableField, propertyLevelCloneable.writableField);
	}
}
}

package dolly {
import dolly.core.dolly_internal;
import dolly.data.ClassLevelCloneable;
import dolly.data.ClassLevelCopyableCloneable;
import dolly.data.PropertyLevelCloneable;
import dolly.data.PropertyLevelCopyableCloneable;

import org.as3commons.reflect.Field;
import org.as3commons.reflect.Type;
import org.flexunit.asserts.assertEquals;
import org.flexunit.asserts.assertNotNull;
import org.flexunit.asserts.assertNull;

use namespace dolly_internal;

public class ClonerTest {

	private var classLevelCloneable:ClassLevelCloneable;
	private var classLevelCloneableType:Type;

	private var classLevelCopyableCloneable:ClassLevelCopyableCloneable;
	private var classLevelCopyableCloneableType:Type;

	private var propertyLevelCloneable:PropertyLevelCloneable;
	private var propertyLevelCloneableType:Type;

	private var propertyLevelCopyableCloneable:PropertyLevelCopyableCloneable;
	private var propertyLevelCopyableCloneableType:Type;

	[Before]
	public function before():void {
		classLevelCloneable = new ClassLevelCloneable();
		classLevelCloneable.property1 = "value 1";
		classLevelCloneable.property2 = "value 2";
		classLevelCloneable.property3 = "value 3";
		classLevelCloneable.writableField = "value 4";
		classLevelCloneableType = Type.forInstance(classLevelCloneable);

		classLevelCopyableCloneable = new ClassLevelCopyableCloneable();
		classLevelCopyableCloneable.property1 = "value 1";
		classLevelCopyableCloneable.property2 = "value 2";
		classLevelCopyableCloneable.property3 = "value 3";
		classLevelCopyableCloneable.writableField = "value 4";
		classLevelCopyableCloneableType = Type.forInstance(classLevelCopyableCloneable);

		propertyLevelCloneable = new PropertyLevelCloneable();
		propertyLevelCloneable.property1 = "value 1";
		propertyLevelCloneable.property2 = "value 2";
		propertyLevelCloneable.property3 = "value 3";
		propertyLevelCloneable.writableField = "value 4";
		propertyLevelCloneableType = Type.forInstance(propertyLevelCloneable);

		propertyLevelCopyableCloneable = new PropertyLevelCopyableCloneable();
		propertyLevelCopyableCloneable.property1 = "value 1";
		propertyLevelCopyableCloneable.property2 = "value 2";
		propertyLevelCopyableCloneable.property3 = "value 3";
		propertyLevelCopyableCloneable.writableField = "value 4";
		propertyLevelCopyableCloneableType = Type.forInstance(propertyLevelCopyableCloneable);
	}

	[After]
	public function after():void {
		classLevelCloneable = null;
		classLevelCloneableType = null;

		classLevelCopyableCloneable = null;
		classLevelCopyableCloneableType = null;

		propertyLevelCloneable = null;
		propertyLevelCloneableType = null;

		propertyLevelCopyableCloneable = null;
		propertyLevelCopyableCloneableType = null;
	}

	[Test]
	public function testGetCloneableFieldsForTypeClassLevelCloneable():void {
		const cloneableFields:Vector.<Field> = Cloner.getCloneableFieldsForType(classLevelCloneableType);
		assertNotNull(cloneableFields);
		assertEquals(4, cloneableFields.length);
	}

	[Test]
	public function testGetCloneableFieldsForTypeClassLevelCopyableCloneable():void {
		const cloneableFields:Vector.<Field> = Cloner.getCloneableFieldsForType(classLevelCopyableCloneableType);
		assertNotNull(cloneableFields);
		assertEquals(4, cloneableFields.length);
	}

	[Test]
	public function testGetCloneableFieldsForTypePropertyLevelCloneable():void {
		const cloneableFields:Vector.<Field> = Cloner.getCloneableFieldsForType(propertyLevelCloneableType);
		assertNotNull(cloneableFields);
		assertEquals(3, cloneableFields.length);
	}

	[Test]
	public function testGetCloneableFieldsForTypePropertyLevelCopyableCloneable():void {
		const cloneableFields:Vector.<Field> = Cloner.getCloneableFieldsForType(propertyLevelCopyableCloneableType);
		assertNotNull(cloneableFields);
		assertEquals(4, cloneableFields.length);
	}

	[Test]
	public function testCloneClassLevelCloneable():void {
		const clone:ClassLevelCloneable = Cloner.clone(classLevelCloneable);
		assertNotNull(clone);
		assertNotNull(clone.property1);
		assertEquals(clone.property1, classLevelCloneable.property1);
		assertNotNull(clone.property2);
		assertEquals(clone.property2, classLevelCloneable.property2);
		assertNotNull(clone.property3);
		assertEquals(clone.property3, classLevelCloneable.property3);
		assertNotNull(clone.writableField);
		assertEquals(clone.writableField, classLevelCloneable.writableField);
	}

	[Test]
	public function testCloneClassLevelCopyableCloneable():void {
		const clone:ClassLevelCopyableCloneable = Cloner.clone(classLevelCopyableCloneable);
		assertNotNull(clone);
		assertNotNull(clone.property1);
		assertEquals(clone.property1, classLevelCloneable.property1);
		assertNotNull(clone.property2);
		assertEquals(clone.property2, classLevelCloneable.property2);
		assertNotNull(clone.property3);
		assertEquals(clone.property3, classLevelCloneable.property3);
		assertNotNull(clone.writableField);
		assertEquals(clone.writableField, classLevelCloneable.writableField);
	}

	[Test]
	public function testCloneWithPropertyLevelMetadata():void {
		const clone1:PropertyLevelCloneable = Cloner.clone(propertyLevelCloneable);
		assertNotNull(clone1);
		assertNull(clone1.property1);
		assertNotNull(clone1.property2);
		assertEquals(clone1.property2, propertyLevelCloneable.property2);
		assertNotNull(clone1.property3);
		assertEquals(clone1.property3, propertyLevelCloneable.property3);
		assertNotNull(clone1.writableField);
		assertEquals(clone1.writableField, propertyLevelCloneable.writableField);

		const clone2:PropertyLevelCopyableCloneable = Cloner.clone(propertyLevelCopyableCloneable);
		assertNotNull(clone2);
		assertNotNull(clone2.property1);
		assertEquals(clone2.property1, propertyLevelCloneable.property1);
		assertNotNull(clone2.property2);
		assertEquals(clone2.property2, propertyLevelCloneable.property2);
		assertNotNull(clone2.property3);
		assertEquals(clone2.property3, propertyLevelCloneable.property3);
		assertNotNull(clone2.writableField);
		assertEquals(clone2.writableField, propertyLevelCloneable.writableField);
	}
}
}

Split method "testWithClassLevelCloneable" to a few.


//
// Nenya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/nenya/
//
// 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.miso.client {

import flash.utils.getTimer;

import flash.display.DisplayObject;
import flash.display.Sprite;

import flash.events.Event;

import flash.geom.Point;
import flash.geom.Rectangle;

import flash.text.TextField;
import flash.text.TextFieldAutoSize;
import flash.text.TextFormat;

import flash.events.MouseEvent;

import mx.core.ClassFactory;

import as3isolib.display.primitive.IsoBox;
import as3isolib.geom.Pt;
import as3isolib.geom.IsoMath;
import as3isolib.display.scene.IsoScene;
import as3isolib.display.IsoView;

import com.threerings.crowd.client.PlaceView;
import com.threerings.crowd.data.PlaceObject;
import com.threerings.util.DelayUtil;
import com.threerings.util.Log;
import com.threerings.util.Map;
import com.threerings.util.Maps;
import com.threerings.util.MathUtil;
import com.threerings.util.Set;
import com.threerings.util.Sets;
import com.threerings.util.StringUtil;
import com.threerings.util.maps.WeakValueMap;
import com.threerings.media.tile.BaseTile;
import com.threerings.media.tile.Colorizer;
import com.threerings.media.tile.NoSuchTileSetError;
import com.threerings.media.tile.TileSet;
import com.threerings.media.tile.TileUtil;
import com.threerings.miso.client.MisoMetricsTransformation;
import com.threerings.miso.client.PrioritizedSceneLayoutRenderer;
import com.threerings.miso.data.MisoSceneModel;
import com.threerings.miso.data.ObjectInfo;
import com.threerings.miso.tile.FringeTile;
import com.threerings.miso.util.MisoContext;
import com.threerings.miso.util.ObjectSet;
import com.threerings.miso.util.MisoSceneMetrics;

public class MisoScenePanel extends Sprite
    implements PlaceView
{
    private var log :Log = Log.getLog(MisoScenePanel);

    public function MisoScenePanel (ctx :MisoContext, metrics :MisoSceneMetrics)
    {
        _ctx = ctx;
        // Excitingly, we get to override this globally for as3isolib...
        IsoMath.transformationObject = new MisoMetricsTransformation(metrics,
            metrics.tilehei * 3 / 4);

        _metrics = metrics;

        _isoView = new IsoView();
        _isoView.setSize(DEF_WIDTH, DEF_HEIGHT);

        _isoView.addEventListener(MouseEvent.CLICK, onClick);

        addChild(_loading = createLoadingPanel());
    }

    /**
     * Creates whatever we want to show while we're waiting to load our exciting scene tile data.
     */
    protected function createLoadingPanel () :DisplayObject
    {
        // By default we just stick up a basic label for this...
        var loadingText :TextField = new TextField();
        loadingText.textColor = 0xFFFFFF;
        var loadingFmt :TextFormat = new TextFormat();
        loadingFmt.size = 24;
        loadingText.defaultTextFormat = loadingFmt;
        loadingText.autoSize = TextFieldAutoSize.LEFT;

        _loadingProgressFunc = function (progress :Number) :void {
            loadingText.text = "Loading... " + int(progress*100) + "%";
        };
        _loadingProgressFunc(0.0);
        return loadingText;
    }

    protected function loadingProgress (progress :Number) :void
    {
        if (_loadingProgressFunc != null) {
            _loadingProgressFunc(progress);
        }
    }

    public function onClick (event :MouseEvent) :void
    {
        var viewPt :Point = _isoView.globalToLocal(new Point(event.stageX, event.stageY));
        moveBy(new Point(viewPt.x - _isoView.width / 2, viewPt.y - _isoView.height / 2));
    }

    public function moveBy (pt :Point) :void
    {
        if (_pendingMoveBy != null) {
            log.info("Already performing a move...");
            return;
        }
        _pendingMoveBy = pt;

        refreshBaseBlockScenes();
    }

    public function setSceneModel (model :MisoSceneModel) :void
    {
        _model = model;
        _ctx.getTileManager().ensureLoaded(_model.getAllTilesets(), function() :void {
                DelayUtil.delayFrame(function() :void {
                    refreshScene();
                    removeChild(_loading);
                    addChild(_isoView);
                });
            }, loadingProgress);
    }

    public function willEnterPlace (plobj :PlaceObject) :void
    {
    }

    public function didLeavePlace (plobj :PlaceObject) :void
    {
    }

    /** Computes the fringe tile for the specified coordinate. */
    public function computeFringeTile (tx :int, ty :int) :BaseTile
    {
        return _ctx.getTileManager().getFringer().getFringeTile(_model, tx, ty, _fringes,
            _masks);
    }

    protected function getBaseBlocks () :Set
    {
        var blocks :Set = Sets.newSetOf(int);

        var size :Point = _isoView.size;

        var xMove :int = (_pendingMoveBy == null ? 0 : _pendingMoveBy.x);
        var yMove :int = (_pendingMoveBy == null ? 0 : _pendingMoveBy.y);

        var minX :int = xMove;
        var maxX :int = size.x + xMove;
        var minY :int = yMove;
        var maxY :int = BOTTOM_BUFFER + size.y + yMove;

        var topLeft :Point = _isoView.localToIso(new Point(minX, minY));
        var topRight :Point = _isoView.localToIso(new Point(maxX, minY));
        var btmLeft :Point = _isoView.localToIso(new Point(minX, maxY));
        var btmRight :Point = _isoView.localToIso(new Point(maxX, maxY));

        for (var yy :int = topRight.y - 1; yy <= btmLeft.y + 1; yy++) {
            for (var xx :int = topLeft.x - 1; xx <= btmRight.x + 1; xx++) {

                // Toss out any that aren't actually in our view.
                var blkTop :Point = _isoView.isoToLocal(new Pt(xx, yy, 0));
                var blkRight :Point =
                    _isoView.isoToLocal(new Pt(xx + SceneBlock.BLOCK_SIZE, yy, 0));
                var blkLeft :Point =
                    _isoView.isoToLocal(new Pt(xx, yy + SceneBlock.BLOCK_SIZE, 0));
                var blkBtm :Point =
                    _isoView.isoToLocal(new Pt(xx + SceneBlock.BLOCK_SIZE,
                        yy + SceneBlock.BLOCK_SIZE, 0));
                if (blkTop.y < maxY &&
                    blkBtm.y > minY &&
                    blkLeft.x < maxX &&
                    blkRight.x > minX) {
                    blocks.add(SceneBlock.getBlockKey(xx, yy));
                }
            }
        }

        return blocks;
    }

    protected function refreshBaseBlockScenes () :void
    {
        var blocks :Set = getBaseBlocks();

        // Keep this to use in our function...
        var thisRef :MisoScenePanel = this;

        var resolving :Boolean = false;

        blocks.forEach(function(blockKey :int) :void {
            if (!_blocks.containsKey(blockKey)) {
                var sceneBlock :SceneBlock =
                    new SceneBlock(blockKey, _objScene, _isoView, _metrics);
                _pendingBlocks.add(sceneBlock);
                sceneBlock.resolve(_ctx, _model, thisRef, blockResolved);
                resolving = true;
            }
        });

        if (!resolving) {
            resolutionComplete();
        }
    }

    protected function blockResolved (resolved :SceneBlock) :void
    {
        if (!_pendingBlocks.contains(resolved)) {
            log.info("Trying to resolve non-pending block???: " + resolved.getKey());
        }
        // Move that guy from pending to ready...
        _readyBlocks.add(resolved);
        _pendingBlocks.remove(resolved);

        if (_pendingBlocks.size() == 0) {
            resolutionComplete();
        }
    }

    protected function resolutionComplete () :void
    {
        // First, we add in our new blocks...
        for each (var newBlock :SceneBlock in _readyBlocks.toArray()) {
            newBlock.render();
            _blocks.put(newBlock.getKey(), newBlock);
        }
        _readyBlocks = Sets.newSetOf(SceneBlock);

        // Force to end of list so it's on top...
        _isoView.removeScene(_objScene);
        _isoView.addScene(_objScene);

        _objScene.render();

        DelayUtil.delayFrame(function() :void {
            // Then we let the scene finally move if it's trying to...
            if (_pendingMoveBy != null) {
                _isoView.centerOnPt(new Pt(_pendingMoveBy.x + _isoView.currentX,
                    _pendingMoveBy.y + _isoView.currentY), false);
                _pendingMoveBy = null;
            }

            // Now, take out any old blocks no longer in our valid blocks.
            var blocks :Set = getBaseBlocks();
            for each (var baseKey :int in _blocks.keys()) {
                if (!blocks.contains(baseKey)) {
                    _blocks.remove(baseKey).release();
                }
            }
        });
    }

    protected function refreshScene () :void
    {
        // Clear it out...
        _isoView.removeAllScenes();

        _objScene = new IsoScene();
        _objScene.layoutRenderer = new ClassFactory(PrioritizedSceneLayoutRenderer);

        refreshBaseBlockScenes();
    }

    /**
     * Derived classes can override this method and provide a colorizer that will be used to
     * colorize the supplied scene object when rendering.
     */
    public function getColorizer (oinfo :ObjectInfo) :Colorizer
    {
        return null;
    }

    protected var _model :MisoSceneModel;

    protected var _isoView :IsoView;

    protected var _ctx :MisoContext;

    protected var _metrics :MisoSceneMetrics;

    /** What we display while we're loading up our tilesets. */
    protected var _loading :DisplayObject;

    /** If we should do something when we hear about progress updates, this is it. */
    protected var _loadingProgressFunc :Function;

    protected var _objScene :IsoScene;

    protected var _blocks :Map = Maps.newMapOf(int);

    protected var _pendingMoveBy :Point;

    protected var _pendingBlocks :Set = Sets.newSetOf(SceneBlock);
    protected var _readyBlocks :Set = Sets.newSetOf(SceneBlock);

    protected var _masks :Map = Maps.newMapOf(int);
    protected var _fringes :Map = new WeakValueMap(Maps.newMapOf(FringeTile));

    protected const DEF_WIDTH :int = 985;
    protected const DEF_HEIGHT :int = 560;

    protected const BOTTOM_BUFFER :int = 250;
}
}

//
// Nenya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/nenya/
//
// 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.miso.client {

import flash.utils.getTimer;

import flash.display.DisplayObject;
import flash.display.Sprite;

import flash.events.Event;

import flash.geom.Point;
import flash.geom.Rectangle;

import flash.text.TextField;
import flash.text.TextFieldAutoSize;
import flash.text.TextFormat;

import flash.events.MouseEvent;

import mx.core.ClassFactory;

import as3isolib.display.primitive.IsoBox;
import as3isolib.geom.Pt;
import as3isolib.geom.IsoMath;
import as3isolib.display.scene.IsoScene;
import as3isolib.display.IsoView;

import com.threerings.crowd.client.PlaceView;
import com.threerings.crowd.data.PlaceObject;
import com.threerings.util.DelayUtil;
import com.threerings.util.Log;
import com.threerings.util.Map;
import com.threerings.util.Maps;
import com.threerings.util.MathUtil;
import com.threerings.util.Set;
import com.threerings.util.Sets;
import com.threerings.util.StringUtil;
import com.threerings.util.maps.WeakValueMap;
import com.threerings.media.tile.BaseTile;
import com.threerings.media.tile.Colorizer;
import com.threerings.media.tile.NoSuchTileSetError;
import com.threerings.media.tile.TileSet;
import com.threerings.media.tile.TileUtil;
import com.threerings.miso.client.MisoMetricsTransformation;
import com.threerings.miso.client.PrioritizedSceneLayoutRenderer;
import com.threerings.miso.data.MisoSceneModel;
import com.threerings.miso.data.ObjectInfo;
import com.threerings.miso.tile.FringeTile;
import com.threerings.miso.util.MisoContext;
import com.threerings.miso.util.ObjectSet;
import com.threerings.miso.util.MisoSceneMetrics;

public class MisoScenePanel extends Sprite
    implements PlaceView
{
    private var log :Log = Log.getLog(MisoScenePanel);

    public function MisoScenePanel (ctx :MisoContext, metrics :MisoSceneMetrics)
    {
        _ctx = ctx;
        // Excitingly, we get to override this globally for as3isolib...
        IsoMath.transformationObject = new MisoMetricsTransformation(metrics,
            metrics.tilehei * 3 / 4);

        _metrics = metrics;

        _isoView = new IsoView();
        _isoView.setSize(DEF_WIDTH, DEF_HEIGHT);

        _isoView.addEventListener(MouseEvent.CLICK, onClick);

        addChild(_loading = createLoadingPanel());
    }

    /**
     * Creates whatever we want to show while we're waiting to load our exciting scene tile data.
     */
    protected function createLoadingPanel () :DisplayObject
    {
        // By default we just stick up a basic label for this...
        var loadingText :TextField = new TextField();
        loadingText.textColor = 0xFFFFFF;
        var loadingFmt :TextFormat = new TextFormat();
        loadingFmt.size = 24;
        loadingText.defaultTextFormat = loadingFmt;
        loadingText.autoSize = TextFieldAutoSize.LEFT;

        _loadingProgressFunc = function (progress :Number) :void {
            loadingText.text = "Loading... " + int(progress*100) + "%";
        };
        _loadingProgressFunc(0.0);
        return loadingText;
    }

    protected function loadingProgress (progress :Number) :void
    {
        if (_loadingProgressFunc != null) {
            _loadingProgressFunc(progress);
        }
    }

    public function onClick (event :MouseEvent) :void
    {
        var viewPt :Point = _isoView.globalToLocal(new Point(event.stageX, event.stageY));
        moveBy(new Point(viewPt.x - _isoView.width / 2, viewPt.y - _isoView.height / 2));
    }

    public function moveBy (pt :Point) :void
    {
        if (_pendingMoveBy != null) {
            log.info("Already performing a move...");
            return;
        }
        _pendingMoveBy = pt;

        refreshBaseBlockScenes();
    }

    public function setSceneModel (model :MisoSceneModel) :void
    {
        _model = model;
        _ctx.getTileManager().ensureLoaded(_model.getAllTilesets(), function() :void {
                DelayUtil.delayFrame(function() :void {
                    refreshScene();
                    removeChild(_loading);
                    addChild(_isoView);
                });
            }, loadingProgress);
    }

    public function willEnterPlace (plobj :PlaceObject) :void
    {
    }

    public function didLeavePlace (plobj :PlaceObject) :void
    {
    }

    /** Computes the fringe tile for the specified coordinate. */
    public function computeFringeTile (tx :int, ty :int) :BaseTile
    {
        return _ctx.getTileManager().getFringer().getFringeTile(_model, tx, ty, _fringes,
            _masks);
    }

    protected function getBaseBlocks () :Set
    {
        var blocks :Set = Sets.newSetOf(int);

        var size :Point = _isoView.size;

        var xMove :int = (_pendingMoveBy == null ? 0 : _pendingMoveBy.x);
        var yMove :int = (_pendingMoveBy == null ? 0 : _pendingMoveBy.y);

        var minX :int = xMove;
        var maxX :int = size.x + xMove;
        var minY :int = yMove;
        var maxY :int = BOTTOM_BUFFER + size.y + yMove;

        var topLeft :Point = _isoView.localToIso(new Point(minX, minY));
        var topRight :Point = _isoView.localToIso(new Point(maxX, minY));
        var btmLeft :Point = _isoView.localToIso(new Point(minX, maxY));
        var btmRight :Point = _isoView.localToIso(new Point(maxX, maxY));

        for (var yy :int = topRight.y - 1; yy <= btmLeft.y + 1; yy++) {
            for (var xx :int = topLeft.x - 1; xx <= btmRight.x + 1; xx++) {

                // Toss out any that aren't actually in our view.
                var blkTop :Point = _isoView.isoToLocal(new Pt(xx, yy, 0));
                var blkRight :Point =
                    _isoView.isoToLocal(new Pt(xx + SceneBlock.BLOCK_SIZE, yy, 0));
                var blkLeft :Point =
                    _isoView.isoToLocal(new Pt(xx, yy + SceneBlock.BLOCK_SIZE, 0));
                var blkBtm :Point =
                    _isoView.isoToLocal(new Pt(xx + SceneBlock.BLOCK_SIZE,
                        yy + SceneBlock.BLOCK_SIZE, 0));
                if (blkTop.y < maxY &&
                    blkBtm.y > minY &&
                    blkLeft.x < maxX &&
                    blkRight.x > minX) {
                    blocks.add(SceneBlock.getBlockKey(xx, yy));
                }
            }
        }

        return blocks;
    }

    protected function refreshBaseBlockScenes () :void
    {
        _resStartTime = getTimer();

        var blocks :Set = getBaseBlocks();

        // Keep this to use in our function...
        var thisRef :MisoScenePanel = this;

        // Postpone calling complete til they're all queued up.
        _skipComplete = true;

        blocks.forEach(function(blockKey :int) :void {
            if (!_blocks.containsKey(blockKey)) {
                var sceneBlock :SceneBlock =
                    new SceneBlock(blockKey, _objScene, _isoView, _metrics);
                _pendingBlocks.add(sceneBlock);
                sceneBlock.resolve(_ctx, _model, thisRef, blockResolved);
            }
        });

        _skipComplete = false;

        if (_pendingBlocks.size() == 0) {
            resolutionComplete();
        }
    }

    protected function blockResolved (resolved :SceneBlock) :void
    {
        if (!_pendingBlocks.contains(resolved)) {
            log.info("Trying to resolve non-pending block???: " + resolved.getKey());
        }
        // Move that guy from pending to ready...
        _readyBlocks.add(resolved);
        _pendingBlocks.remove(resolved);
        if (_pendingBlocks.size() == 0 && !_skipComplete) {
            resolutionComplete();
        }
    }

    protected function resolutionComplete () :void
    {
        // First, we add in our new blocks...
        for each (var newBlock :SceneBlock in _readyBlocks.toArray()) {
            newBlock.render();
            _blocks.put(newBlock.getKey(), newBlock);
        }
        _readyBlocks = Sets.newSetOf(SceneBlock);

        // Force to end of list so it's on top...
        _isoView.removeScene(_objScene);
        _isoView.addScene(_objScene);

        _objScene.render();

        // Then we let the scene finally move if it's trying to...
        if (_pendingMoveBy != null) {
            _isoView.centerOnPt(new Pt(_pendingMoveBy.x + _isoView.currentX,
                _pendingMoveBy.y + _isoView.currentY), false);
            _pendingMoveBy = null;
        }

        // Now, take out any old blocks no longer in our valid blocks.
        var blocks :Set = getBaseBlocks();
        for each (var baseKey :int in _blocks.keys()) {
            if (!blocks.contains(baseKey)) {
                _blocks.remove(baseKey).release();
            }
        }

        trace("Scene Block Resolution took: " + (getTimer() - _resStartTime) + "ms");
    }

    protected function refreshScene () :void
    {
        // Clear it out...
        _isoView.removeAllScenes();

        _objScene = new IsoScene();
        _objScene.layoutRenderer = new ClassFactory(PrioritizedSceneLayoutRenderer);

        refreshBaseBlockScenes();
    }

    /**
     * Derived classes can override this method and provide a colorizer that will be used to
     * colorize the supplied scene object when rendering.
     */
    public function getColorizer (oinfo :ObjectInfo) :Colorizer
    {
        return null;
    }

    protected var _model :MisoSceneModel;

    protected var _isoView :IsoView;

    protected var _ctx :MisoContext;

    protected var _metrics :MisoSceneMetrics;

    /** What we display while we're loading up our tilesets. */
    protected var _loading :DisplayObject;

    /** If we should do something when we hear about progress updates, this is it. */
    protected var _loadingProgressFunc :Function;

    protected var _objScene :IsoScene;

    protected var _blocks :Map = Maps.newMapOf(int);

    protected var _pendingMoveBy :Point;

    protected var _pendingBlocks :Set = Sets.newSetOf(SceneBlock);
    protected var _readyBlocks :Set = Sets.newSetOf(SceneBlock);

    protected var _masks :Map = Maps.newMapOf(int);
    protected var _fringes :Map = new WeakValueMap(Maps.newMapOf(FringeTile));

    protected var _resStartTime :int;

    protected var _skipComplete :Boolean

    protected const DEF_WIDTH :int = 985;
    protected const DEF_HEIGHT :int = 560;

    protected const BOTTOM_BUFFER :int = 250;
}
}

Fix a bug where we were finishing resolution of the entire scene multiple times if some blocks were basically already resolved.

git-svn-id: b675b909355d5cf946977f44a8ec5a6ceb3782e4@957 ed5b42cb-e716-0410-a449-f6a68f950b19