/**
 * User: MerlinDS
 * Date: 18.07.2014
 * Time: 17:42
 */
package com.merlinds.miracle_tool.viewer {
	import com.bit101.components.List;
	import com.bit101.components.Window;
	import com.merlinds.debug.log;
	import com.merlinds.miracle.Miracle;
	import com.merlinds.miracle.animations.AnimationHelper;
	import com.merlinds.miracle.display.MiracleDisplayObject;
	import com.merlinds.miracle.utils.Asset;
	import com.merlinds.miracle.utils.MafReader;
	import com.merlinds.miracle_tool.models.AppModel;

	import flash.display.Sprite;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.events.Event;
	import flash.filesystem.FileMode;
	import flash.filesystem.FileStream;
	import flash.html.script.Package;
	import flash.utils.ByteArray;

	[SWF(backgroundColor="0x333333", frameRate=60)]
	public class ViewerView extends Sprite {

		private var _model:AppModel;
		private var _assets:Vector.<Asset>;
		private var _window:Window;
		private var _name:String;
		private var _current:MiracleDisplayObject;

		public function ViewerView(model:AppModel = null) {
			super();
			_model = model;
			if(_model == null){
				_model = new AppModel();
			}

			this.addEventListener(Event.ADDED_TO_STAGE, this.initialize);
		}
		//==============================================================================
		//{region							PUBLIC METHODS
		//} endregion PUBLIC METHODS ===================================================

		//==============================================================================
		//{region						PRIVATE\PROTECTED METHODS
		private function initialize(event:Event):void {
			this.removeEventListener(event.type, this.initialize);
			this.stage.scaleMode = StageScaleMode.NO_SCALE;
			this.stage.align = StageAlign.TOP_LEFT;
			Miracle.start(this.stage, this.createHandler, true);
		}

		private function choseAnimation():void {
			//find animation asset and add all of animations to chose list
			var n:int = _assets.length;
			for(var i:int = 0; i < n; i++){
				var asset:Asset = _assets[i];
				if(asset.type == Asset.TIMELINE_TYPE){
					//parse output
					_window = new Window(this, 0, 0, "Chose animation");
					var list:List = new List(_window, 0, 0, this.getAnimations(asset.output));
					_window.x = this.stage.stageWidth - _window.width;
					list.addEventListener(Event.SELECT, this.selectAnimationHandler);
				}
			}
			var w2:Window = new Window(this, _window.x, _window.y + _window.height + 10, "FPS");
			list = new List(w2, 0, 0, [1, 5, 16, 24, 30, 35, 40, 60]);
			w2.height = 120;
			list.addEventListener(Event.SELECT, this.selectFpsHandler);
		}

		[Inline]
		private function getAnimations(bytes:ByteArray):Array {
			var result:Array = [];
			var reader:MafReader = new MafReader();
			reader.execute(bytes);
			var animations:Vector.<AnimationHelper> = reader.animations;
			for each(var animation:AnimationHelper in animations){
				result.push(animation.name);
			}
			return result;
		}
		//} endregion PRIVATE\PROTECTED METHODS ========================================

		//==============================================================================
		//{region							EVENTS HANDLERS
		private function createHandler(animation:Boolean = false):void {
			//TODO: Show view if it exit
			if(animation || _model.viewerInput == null){
				_model.viewerInput = _model.lastFileDirection;
				_model.viewerInput.addEventListener(Event.SELECT, this.selectFileHandler);
				_model.viewerInput.browseForOpen("Open " + animation ? "Animation" : "Sprite"
						+ "file that you want to view");
			}
		}

		private function selectFileHandler(event:Event):void {
			_model.viewerInput.removeEventListener(event.type, this.selectFileHandler);
			_model.lastFileDirection = _model.viewerInput.parent;
			var byteArray:ByteArray = new ByteArray();
			var stream:FileStream = new FileStream();
			stream.open(_model.viewerInput, FileMode.READ);
			stream.readBytes(byteArray);
			stream.close();
			//parse
			if(_assets == null)_assets = new <Asset>[];
			var asset:Asset = new Asset(_model.viewerInput.name, byteArray);
			if(asset.type == Asset.TIMELINE_TYPE){
				_name = asset.name;
			}
			_assets.push(asset);
			if(_assets.length > 1){
				this.choseAnimation();
				Miracle.createScene(_assets, 1);
				Miracle.resume();
			}else{
				this.createHandler(true);
			}
		}

		private function selectAnimationHandler(event:Event):void {
			var list:List = event.target as List;
			//add animation to miracle
			var animation:String = list.selectedItem.toString();
			log(this, "selectAnimationHandler", animation);
			Miracle.currentScene.createAnimation(_name, _name + "." + animation, 60)
					.addEventListener(Event.ADDED_TO_STAGE, this.imageAddedToStage);
		}

		private function imageAddedToStage(event:Event):void {
			var target:MiracleDisplayObject = event.target as MiracleDisplayObject;
			target.moveTO(
					500,300 /*this.stage.stageWidth - target.width >> 1,
					this.stage.stageHeight - target.height >> 1*/
			);
			_current = target;
			_current.currentFrame = 0;
//			_current.stop();
		}

		private function selectFpsHandler(event:Event):void {
			var list:List = event.target as List;
			//add animation to miracle
			if(_current != null){
				_current.fps = int(list.selectedItem);
			}
		}
		//} endregion EVENTS HANDLERS ==================================================

		//==============================================================================
		//{region							GETTERS/SETTERS
		//} endregion GETTERS/SETTERS ==================================================
	}
}

/**
 * User: MerlinDS
 * Date: 18.07.2014
 * Time: 17:42
 */
package com.merlinds.miracle_tool.viewer {
	import com.bit101.components.List;
	import com.bit101.components.Window;
	import com.merlinds.debug.log;
	import com.merlinds.miracle.Miracle;
	import com.merlinds.miracle.animations.AnimationHelper;
	import com.merlinds.miracle.display.MiracleDisplayObject;
	import com.merlinds.miracle.utils.Asset;
	import com.merlinds.miracle.utils.MafReader;
	import com.merlinds.miracle_tool.models.AppModel;

	import flash.display.Sprite;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.events.Event;
	import flash.filesystem.FileMode;
	import flash.filesystem.FileStream;
	import flash.utils.ByteArray;

	[SWF(backgroundColor="0x333333", frameRate=60)]
	public class ViewerView extends Sprite {

		private var _model:AppModel;
		private var _assets:Vector.<Asset>;
		private var _window:Window;
		private var _name:String;
		private var _current:MiracleDisplayObject;

		public function ViewerView(model:AppModel = null) {
			super();
			_model = model;
			if(_model == null){
				_model = new AppModel();
			}

			this.addEventListener(Event.ADDED_TO_STAGE, this.initialize);
		}
		//==============================================================================
		//{region							PUBLIC METHODS
		//} endregion PUBLIC METHODS ===================================================

		//==============================================================================
		//{region						PRIVATE\PROTECTED METHODS
		private function initialize(event:Event):void {
			this.removeEventListener(event.type, this.initialize);
			this.stage.scaleMode = StageScaleMode.NO_SCALE;
			this.stage.align = StageAlign.TOP_LEFT;
			Miracle.start(this.stage, this.createHandler, true);
		}

		private function choseAnimation():void {
			//find animation asset and add all of animations to chose list
			var n:int = _assets.length;
			for(var i:int = 0; i < n; i++){
				var asset:Asset = _assets[i];
				if(asset.type == Asset.TIMELINE_TYPE){
					//parse output
					_window = new Window(this, 0, 0, "Chose animation");
					var list:List = new List(_window, 0, 0, this.getAnimations(asset.output));
					_window.x = this.stage.stageWidth - _window.width;
					list.addEventListener(Event.SELECT, this.selectAnimationHandler);
				}
			}
			var w2:Window = new Window(this, _window.x, _window.y + _window.height + 10, "FPS");
			list = new List(w2, 0, 0, [1, 5, 16, 24, 30, 35, 40, 60]);
			w2.height = 120;
			list.addEventListener(Event.SELECT, this.selectFpsHandler);
		}

		[Inline]
		private function getAnimations(bytes:ByteArray):Array {
			var result:Array = [];
			var reader:MafReader = new MafReader();
			reader.execute(bytes);
			var animations:Vector.<AnimationHelper> = reader.animations;
			for each(var animation:AnimationHelper in animations){
				result.push(animation.name);
			}
			return result;
		}
		//} endregion PRIVATE\PROTECTED METHODS ========================================

		//==============================================================================
		//{region							EVENTS HANDLERS
		private function createHandler(animation:Boolean = false):void {
			//TODO: Show view if it exit
			if(animation || _model.viewerInput == null){
				_model.viewerInput = _model.lastFileDirection;
				_model.viewerInput.addEventListener(Event.SELECT, this.selectFileHandler);
				_model.viewerInput.browseForOpen("Open " + animation ? "Animation" : "Sprite"
						+ "file that you want to view");
			}
		}

		private function selectFileHandler(event:Event):void {
			_model.viewerInput.removeEventListener(event.type, this.selectFileHandler);
			_model.lastFileDirection = _model.viewerInput.parent;
			var byteArray:ByteArray = new ByteArray();
			var stream:FileStream = new FileStream();
			stream.open(_model.viewerInput, FileMode.READ);
			stream.readBytes(byteArray);
			stream.close();
			//parse
			if(_assets == null)_assets = new <Asset>[];
			var asset:Asset = new Asset(_model.viewerInput.name, byteArray);
			if(asset.type == Asset.TIMELINE_TYPE){
				_name = asset.name;
			}
			_assets.push(asset);
			if(_assets.length > 1){
				this.choseAnimation();
				Miracle.createScene(_assets, 1);
				Miracle.resume();
			}else{
				this.createHandler(true);
			}
		}

		private function selectAnimationHandler(event:Event):void {
			var list:List = event.target as List;
			//add animation to miracle
			var animation:String = list.selectedItem.toString();
			log(this, "selectAnimationHandler", animation);
			if(_current == null){
				_current = Miracle.currentScene.createAnimation(_name, _name + "." + animation, 60);
				_current.addEventListener(Event.ADDED_TO_STAGE, this.imageAddedToStage);
			}else{
				_current.animation = _name + "." + animation;
			}
		}

		private function imageAddedToStage(event:Event):void {
			_current.moveTO(
					this.stage.stageWidth - _current.width >> 1,
					this.stage.stageHeight - _current.height >> 1
			);
		}

		private function selectFpsHandler(event:Event):void {
			var list:List = event.target as List;
			//add animation to miracle
			if(_current != null){
				_current.fps = int(list.selectedItem);
			}
		}
		//} endregion EVENTS HANDLERS ==================================================

		//==============================================================================
		//{region							GETTERS/SETTERS
		//} endregion GETTERS/SETTERS ==================================================
	}
}

add change animation ability

add change animation ability


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

    This file is a part of Weave.

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

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

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

package weave.data.AttributeColumns
{
	import flash.utils.getQualifiedClassName;

	import weave.api.WeaveAPI;
	import weave.api.core.ILinkableObject;
	import weave.api.data.IAttributeColumn;
	import weave.api.data.IColumnReference;
	import weave.api.data.IColumnWrapper;
	import weave.api.data.IQualifiedKey;
	import weave.api.detectLinkableObjectChange;
	import weave.api.registerLinkableChild;
	import weave.api.setSessionState;
	import weave.core.CallbackCollection;
	import weave.core.LinkableDynamicObject;
	import weave.core.SessionManager;
	import weave.utils.ColumnUtils;

	/**
	 * This provides a wrapper for a referenced column.
	 *
	 * @author adufilie
	 */
	public class ReferencedColumn extends CallbackCollection implements IColumnWrapper
	{
		/**
		 * This is a reference to another column.
		 */
		public const dynamicColumnReference:LinkableDynamicObject = registerLinkableChild(this, new LinkableDynamicObject(IColumnReference));

		/**
		 * The trigger counter value at the last time the internal column was retrieved.
		 */
		private var _dynamicRefTriggerCounter:uint = 0;
		/**
		 * the internal referenced column
		 */
		protected var _internalColumn:IAttributeColumn = null;

		/**
		 * This is the actual IColumnReference object inside dynamicColumnReference.
		 */
		public function get internalColumnReference():IColumnReference
		{
			return dynamicColumnReference.internalObject as IColumnReference;
		}

		/**
		 * @inheritDoc
		 */
		public function getInternalColumn():IAttributeColumn
		{
			if (_dynamicRefTriggerCounter != dynamicColumnReference.triggerCounter)
			{
				_dynamicRefTriggerCounter = dynamicColumnReference.triggerCounter;

				var newColumn:IAttributeColumn = WeaveAPI.AttributeColumnCache.getColumn(internalColumnReference);
				// do nothing if this is the same column
				if (_internalColumn != newColumn)
				{
					if (_internalColumn)
						_internalColumn.removeCallback(triggerCallbacks);

					_internalColumn = newColumn;

					if (_internalColumn)
						_internalColumn.addImmediateCallback(this, triggerCallbacks, false, true);
				}
			}
			return _internalColumn;
		}

		/************************************
		 * Begin IAttributeColumn interface
		 ************************************/

		public function getMetadata(attributeName:String):String
		{
			return getInternalColumn() ? _internalColumn.getMetadata(attributeName) : null;
		}

		/**
		 * @return the keys associated with this column.
		 */
		public function get keys():Array
		{
			return getInternalColumn() ? _internalColumn.keys : [];
		}

		/**
		 * @param key A key to test.
		 * @return true if the key exists in this IKeySet.
		 */
		public function containsKey(key:IQualifiedKey):Boolean
		{
			return getInternalColumn() && _internalColumn.containsKey(key);
		}

		/**
		 * getValueFromKey
		 * @param key A key of the type specified by keyType.
		 * @return The value associated with the given key.
		 */
		public function getValueFromKey(key:IQualifiedKey, dataType:Class = null):*
		{
			if (getInternalColumn())
				return _internalColumn.getValueFromKey(key, dataType);
			return undefined;
		}

		public function toString():String
		{
			return debugId(this) + '(' + ColumnUtils.getTitle(this) + ')';
		}

		// backwards compatibility
		[Deprecated(replacement="dynamicColumnReference")] public function set columnReference(value:Object):void
		{
			setSessionState(dynamicColumnReference, value);
		}
	}
}

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

    This file is a part of Weave.

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

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

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

package weave.data.AttributeColumns
{
	import flash.utils.getQualifiedClassName;

	import weave.api.WeaveAPI;
	import weave.api.core.ILinkableObject;
	import weave.api.data.IAttributeColumn;
	import weave.api.data.IColumnReference;
	import weave.api.data.IColumnWrapper;
	import weave.api.data.IQualifiedKey;
	import weave.api.detectLinkableObjectChange;
	import weave.api.registerLinkableChild;
	import weave.api.setSessionState;
	import weave.core.CallbackCollection;
	import weave.core.LinkableDynamicObject;
	import weave.core.SessionManager;
	import weave.utils.ColumnUtils;

	/**
	 * This provides a wrapper for a referenced column.
	 *
	 * @author adufilie
	 */
	public class ReferencedColumn extends CallbackCollection implements IColumnWrapper
	{
		/**
		 * This is a reference to another column.
		 */
		public const dynamicColumnReference:LinkableDynamicObject = registerLinkableChild(this, new LinkableDynamicObject(IColumnReference));

		/**
		 * The trigger counter value at the last time the internal column was retrieved.
		 */
		private var _dynamicRefTriggerCounter:uint = 0;
		/**
		 * the internal referenced column
		 */
		protected var _internalColumn:IAttributeColumn = null;

		/**
		 * This is the actual IColumnReference object inside dynamicColumnReference.
		 */
		public function get internalColumnReference():IColumnReference
		{
			return dynamicColumnReference.internalObject as IColumnReference;
		}

		/**
		 * @inheritDoc
		 */
		public function getInternalColumn():IAttributeColumn
		{
			if (_dynamicRefTriggerCounter != dynamicColumnReference.triggerCounter)
			{
				_dynamicRefTriggerCounter = dynamicColumnReference.triggerCounter;

				var newColumn:IAttributeColumn = WeaveAPI.AttributeColumnCache.getColumn(internalColumnReference);
				// do nothing if this is the same column
				if (_internalColumn != newColumn)
				{
					if (_internalColumn != null)
						(WeaveAPI.SessionManager as SessionManager).unregisterLinkableChild(this, _internalColumn);

					_internalColumn = newColumn;

					if (_internalColumn != null)
						registerLinkableChild(this, _internalColumn);
				}
			}
			return _internalColumn;
		}

		/************************************
		 * Begin IAttributeColumn interface
		 ************************************/

		public function getMetadata(attributeName:String):String
		{
			return getInternalColumn() ? _internalColumn.getMetadata(attributeName) : null;
		}

		/**
		 * @return the keys associated with this column.
		 */
		public function get keys():Array
		{
			return getInternalColumn() ? _internalColumn.keys : [];
		}

		/**
		 * @param key A key to test.
		 * @return true if the key exists in this IKeySet.
		 */
		public function containsKey(key:IQualifiedKey):Boolean
		{
			return getInternalColumn() && _internalColumn.containsKey(key);
		}

		/**
		 * getValueFromKey
		 * @param key A key of the type specified by keyType.
		 * @return The value associated with the given key.
		 */
		public function getValueFromKey(key:IQualifiedKey, dataType:Class = null):*
		{
			if (getInternalColumn())
				return _internalColumn.getValueFromKey(key, dataType);
			return undefined;
		}

		public function toString():String
		{
			return debugId(this) + '(' + ColumnUtils.getTitle(this) + ')';
		}

		// backwards compatibility
		[Deprecated(replacement="dynamicColumnReference")] public function set columnReference(value:Object):void
		{
			setSessionState(dynamicColumnReference, value);
		}
	}
}

undo recent change

undo recent change

Change-Id: I2b39cb43581c9bb84546b6e99c9164f8d1b23b0f


////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2008-2013 Esri. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
////////////////////////////////////////////////////////////////////////////////
package com.esri.builder.controllers.supportClasses
{

import com.esri.builder.model.Model;
import com.esri.builder.model.WidgetType;
import com.esri.builder.model.WidgetTypeRegistryModel;
import com.esri.builder.supportClasses.LogUtil;
import com.esri.builder.views.BuilderAlert;

import flash.events.Event;
import flash.events.EventDispatcher;
import flash.filesystem.File;
import flash.filesystem.FileMode;
import flash.filesystem.FileStream;
import flash.system.ApplicationDomain;

import modules.IBuilderModule;
import modules.supportClasses.CustomXMLModule;

import mx.core.FlexGlobals;
import mx.events.ModuleEvent;
import mx.logging.ILogger;
import mx.logging.Log;
import mx.modules.IModuleInfo;
import mx.modules.ModuleManager;
import mx.resources.ResourceManager;

public class WidgetTypeLoader extends EventDispatcher
{
    private static const LOG:ILogger = LogUtil.createLogger(WidgetTypeLoader);

    private var _moduleInfoArr:Array = [];
    private var _widgetTypeArr:Array = [];

    public function loadWidgetTypes():void
    {
        if (Log.isInfo())
        {
            LOG.info('Loading modules...');
        }

        const modulesDirectory:File = WellKnownDirectories.getInstance().bundledModules;
        loadCustomWidgetTypes(WellKnownDirectories.getInstance().customModules);

        // Find all swf files under the modules folder.
        const swfList:Array = [];
        if (modulesDirectory.isDirectory)
        {
            getSWFList(modulesDirectory, swfList);
        }
        // Load the found modules.
        swfList.forEach(function(file:File, index:int, source:Array):void
        {
            if (Log.isDebug())
            {
                LOG.debug('loading module {0}', file.url);
            }
            const moduleInfo:IModuleInfo = ModuleManager.getModule(file.url);
            _moduleInfoArr.push(moduleInfo);
            moduleInfo.addEventListener(ModuleEvent.READY, moduleInfo_readyHandler);
            moduleInfo.addEventListener(ModuleEvent.ERROR, moduleInfo_errorHandler);
            moduleInfo.load(ApplicationDomain.currentDomain, null, null, FlexGlobals.topLevelApplication.moduleFactory);
        });

        checkIfNoMoreModuleInfosLeft();
    }

    private function loadCustomWidgetTypes(modulesDirectory:File):void
    {
        var moduleDirectoryContents:Array = modulesDirectory.getDirectoryListing();
        for each (var fileOrFolder:File in moduleDirectoryContents)
        {
            loadCustomWidgetTypeConfig(fileOrFolder);
        }
    }

    public function loadCustomWidgetTypeConfig(configFile:File):void
    {
        const customModuleFileName:RegExp = /^.*Module\.xml$/;
        if (!configFile.isDirectory && customModuleFileName.test(configFile.name))
        {
            var customModule:IBuilderModule = createCustomModuleFromConfig(configFile);
            if (customModule)
            {
                WidgetTypeRegistryModel.getInstance().widgetTypeRegistry.addWidgetType(new WidgetType(customModule));
            }
        }
    }

    private function createCustomModuleFromConfig(configFile:File):IBuilderModule
    {
        var fileStream:FileStream = new FileStream();
        var customModule:CustomXMLModule;
        try
        {
            fileStream.open(configFile, FileMode.READ);
            const configXML:XML = XML(fileStream.readUTFBytes(fileStream.bytesAvailable));
            customModule = parseCustomModule(configXML);
        }
        catch (e:Error)
        {
            if (Log.isWarn())
            {
                LOG.warn('Error creating custom module {0}', configFile.nativePath);
            }
        }
        finally
        {
            fileStream.close();
        }

        return customModule;
    }

    private function parseCustomModule(configXML:XML):CustomXMLModule
    {
        var customModule:CustomXMLModule = new CustomXMLModule();
        customModule.widgetName = configXML.name;
        customModule.isOpenByDefault = (configXML.openbydefault == 'true');

        var widgetLabel:String = configXML.label[0];
        var widgetDescription:String = configXML.description[0];
        var widgetHelpURL:String = configXML.helpurl[0];
        var widgetConfiguration:String = configXML.configuration[0];
        var widgetVersion:String = configXML.widgetversion[0];

        customModule.widgetIconLocation = createWidgetIconPath(configXML.icon[0], customModule.widgetName);
        customModule.widgetLabel = widgetLabel ? widgetLabel : customModule.widgetName;
        customModule.widgetVersion = widgetVersion;
        customModule.widgetDescription = widgetDescription ? widgetDescription : "";
        customModule.widgetHelpURL = widgetHelpURL ? widgetHelpURL : "";
        customModule.configXML = widgetConfiguration ? widgetConfiguration : "<configuration></configuration>";

        return customModule;
    }

    private function createWidgetIconPath(iconPath:String, widgetName:String):String
    {
        var widgetIconPath:String;

        if (iconPath)
        {
            widgetIconPath = "widgets/" + widgetName + "/" + iconPath;
        }
        else
        {
            widgetIconPath = "assets/images/i_widget.png";
        }

        return widgetIconPath;
    }

    /**
     * This will go through all sub folders in 'modules' looking for .swf files.
     */
    private function getSWFList(parentFile:File, arr:Array):void
    {
        const moduleFileName:RegExp = /^.*Module\.swf$/;
        const list:Array = parentFile.getDirectoryListing();
        list.forEach(function(file:File, index:int, source:Array):void
        {
            if (file.isDirectory)
            {
                getSWFList(file, arr);
            }
            else if (moduleFileName.test(file.name))
            {
                arr.push(file);
            }
        }
        );
    }

    private function moduleInfo_readyHandler(event:ModuleEvent):void
    {
        var moduleInfo:IModuleInfo = event.currentTarget as IModuleInfo;
        moduleInfo.removeEventListener(ModuleEvent.READY, moduleInfo_readyHandler);
        moduleInfo.removeEventListener(ModuleEvent.ERROR, moduleInfo_errorHandler);

        const builderModule:IBuilderModule = event.module.factory.create() as IBuilderModule;
        if (builderModule)
        {
            const widgetType:WidgetType = new WidgetType(builderModule);
            _widgetTypeArr.push(widgetType);

            // Resolve the object
            //            FlexGlobals.topLevelApplication.registry.resolve(builderModule, ApplicationDomain.currentDomain);
            if (Log.isDebug())
            {
                LOG.debug('Module {0} is resolved', widgetType.name);
            }

            removeModuleInfo(event.module);
        }
    }

    private function removeModuleInfo(moduleInfo:IModuleInfo):void
    {
        const index:int = _moduleInfoArr.indexOf(moduleInfo);
        if (index > -1)
        {
            _moduleInfoArr.splice(index, 1);
            checkIfNoMoreModuleInfosLeft();
        }
    }

    private function checkIfNoMoreModuleInfosLeft():void
    {
        if (_moduleInfoArr.length === 0)
        {
            sortAndAssignWidgetTypes();
        }
    }

    private function sortAndAssignWidgetTypes():void
    {
        if (Log.isInfo())
        {
            LOG.info('All modules resolved');
        }

        _widgetTypeArr.sort(compareWidgetTypes);

        var widgetTypes:Array = _widgetTypeArr.filter(widgetTypeFilter);
        for each (var widgetType:WidgetType in widgetTypes)
        {
            WidgetTypeRegistryModel.getInstance().widgetTypeRegistry.addWidgetType(widgetType);
        }

        var layoutWidgetTypes:Array = _widgetTypeArr.filter(layoutWidgetTypeFilter);
        for each (var layoutWidgetType:WidgetType in layoutWidgetTypes)
        {
            WidgetTypeRegistryModel.getInstance().layoutWidgetTypeRegistry.addWidgetType(layoutWidgetType);
        }

        function widgetTypeFilter(item:WidgetType, index:int, source:Array):Boolean
        {
            return item.isManaged;
        }
        function layoutWidgetTypeFilter(item:WidgetType, index:int, source:Array):Boolean
        {
            return !item.isManaged;
        }

        dispatchEvent(new Event(Event.COMPLETE));
    }

    private function compareWidgetTypes(lhs:WidgetType, rhs:WidgetType):int
    {
        const lhsLabel:String = lhs.label.toLowerCase();
        const rhsLabel:String = rhs.label.toLowerCase();
        if (lhsLabel < rhsLabel)
        {
            return -1;
        }
        if (lhsLabel > rhsLabel)
        {
            return 1;
        }
        return 0;
    }

    private function moduleInfo_errorHandler(event:ModuleEvent):void
    {
        if (Log.isWarn())
        {
            LOG.warn('Module error: {0}', event.errorText);
        }

        var moduleInfo:IModuleInfo = event.currentTarget as IModuleInfo;
        moduleInfo.removeEventListener(ModuleEvent.ERROR, moduleInfo_errorHandler);
        moduleInfo.removeEventListener(ModuleEvent.READY, moduleInfo_readyHandler);

        removeModuleInfo(event.module);
        Model.instance.status = event.errorText;
        BuilderAlert.show(event.errorText,
                          ResourceManager.getInstance().getString('BuilderStrings', 'error'));
    }
}
}

////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2008-2013 Esri. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
////////////////////////////////////////////////////////////////////////////////
package com.esri.builder.controllers.supportClasses
{

import com.esri.builder.model.Model;
import com.esri.builder.model.WidgetType;
import com.esri.builder.model.WidgetTypeRegistryModel;
import com.esri.builder.supportClasses.LogUtil;
import com.esri.builder.views.BuilderAlert;

import flash.events.Event;
import flash.events.EventDispatcher;
import flash.filesystem.File;
import flash.filesystem.FileMode;
import flash.filesystem.FileStream;
import flash.system.ApplicationDomain;
import flash.utils.ByteArray;

import modules.IBuilderModule;
import modules.supportClasses.CustomXMLModule;

import mx.core.FlexGlobals;
import mx.events.ModuleEvent;
import mx.logging.ILogger;
import mx.logging.Log;
import mx.modules.IModuleInfo;
import mx.modules.ModuleManager;
import mx.resources.ResourceManager;

public class WidgetTypeLoader extends EventDispatcher
{
    private static const LOG:ILogger = LogUtil.createLogger(WidgetTypeLoader);

    private var _moduleInfoArr:Array = [];
    private var _widgetTypeArr:Array = [];

    public function loadWidgetTypes():void
    {
        if (Log.isInfo())
        {
            LOG.info('Loading modules...');
        }

        const modulesDirectory:File = WellKnownDirectories.getInstance().bundledModules;
        loadCustomWidgetTypes(WellKnownDirectories.getInstance().customModules);

        // Find all swf files under the modules folder.
        const swfList:Array = [];
        if (modulesDirectory.isDirectory)
        {
            getSWFList(modulesDirectory, swfList);
        }
        // Load the found modules.
        swfList.forEach(function(file:File, index:int, source:Array):void
        {
            if (Log.isDebug())
            {
                LOG.debug('loading module {0}', file.url);
            }

            var fileBytes:ByteArray = new ByteArray();
            var fileStream:FileStream = new FileStream();
            fileStream.open(file, FileMode.READ);
            fileStream.readBytes(fileBytes);
            fileStream.close();

            const moduleInfo:IModuleInfo = ModuleManager.getModule(file.url);
            _moduleInfoArr.push(moduleInfo);
            moduleInfo.addEventListener(ModuleEvent.READY, moduleInfo_readyHandler);
            moduleInfo.addEventListener(ModuleEvent.ERROR, moduleInfo_errorHandler);
            moduleInfo.load(ApplicationDomain.currentDomain, null, fileBytes, FlexGlobals.topLevelApplication.moduleFactory);
        });

        checkIfNoMoreModuleInfosLeft();
    }

    private function loadCustomWidgetTypes(modulesDirectory:File):void
    {
        var moduleDirectoryContents:Array = modulesDirectory.getDirectoryListing();
        for each (var fileOrFolder:File in moduleDirectoryContents)
        {
            loadCustomWidgetTypeConfig(fileOrFolder);
        }
    }

    public function loadCustomWidgetTypeConfig(configFile:File):void
    {
        const customModuleFileName:RegExp = /^.*Module\.xml$/;
        if (!configFile.isDirectory && customModuleFileName.test(configFile.name))
        {
            var customModule:IBuilderModule = createCustomModuleFromConfig(configFile);
            if (customModule)
            {
                WidgetTypeRegistryModel.getInstance().widgetTypeRegistry.addWidgetType(new WidgetType(customModule));
            }
        }
    }

    private function createCustomModuleFromConfig(configFile:File):IBuilderModule
    {
        var fileStream:FileStream = new FileStream();
        var customModule:CustomXMLModule;
        try
        {
            fileStream.open(configFile, FileMode.READ);
            const configXML:XML = XML(fileStream.readUTFBytes(fileStream.bytesAvailable));
            customModule = parseCustomModule(configXML);
        }
        catch (e:Error)
        {
            if (Log.isWarn())
            {
                LOG.warn('Error creating custom module {0}', configFile.nativePath);
            }
        }
        finally
        {
            fileStream.close();
        }

        return customModule;
    }

    private function parseCustomModule(configXML:XML):CustomXMLModule
    {
        var customModule:CustomXMLModule = new CustomXMLModule();
        customModule.widgetName = configXML.name;
        customModule.isOpenByDefault = (configXML.openbydefault == 'true');

        var widgetLabel:String = configXML.label[0];
        var widgetDescription:String = configXML.description[0];
        var widgetHelpURL:String = configXML.helpurl[0];
        var widgetConfiguration:String = configXML.configuration[0];
        var widgetVersion:String = configXML.widgetversion[0];

        customModule.widgetIconLocation = createWidgetIconPath(configXML.icon[0], customModule.widgetName);
        customModule.widgetLabel = widgetLabel ? widgetLabel : customModule.widgetName;
        customModule.widgetVersion = widgetVersion;
        customModule.widgetDescription = widgetDescription ? widgetDescription : "";
        customModule.widgetHelpURL = widgetHelpURL ? widgetHelpURL : "";
        customModule.configXML = widgetConfiguration ? widgetConfiguration : "<configuration></configuration>";

        return customModule;
    }

    private function createWidgetIconPath(iconPath:String, widgetName:String):String
    {
        var widgetIconPath:String;

        if (iconPath)
        {
            widgetIconPath = "widgets/" + widgetName + "/" + iconPath;
        }
        else
        {
            widgetIconPath = "assets/images/i_widget.png";
        }

        return widgetIconPath;
    }

    /**
     * This will go through all sub folders in 'modules' looking for .swf files.
     */
    private function getSWFList(parentFile:File, arr:Array):void
    {
        const moduleFileName:RegExp = /^.*Module\.swf$/;
        const list:Array = parentFile.getDirectoryListing();
        list.forEach(function(file:File, index:int, source:Array):void
        {
            if (file.isDirectory)
            {
                getSWFList(file, arr);
            }
            else if (moduleFileName.test(file.name))
            {
                arr.push(file);
            }
        }
        );
    }

    private function moduleInfo_readyHandler(event:ModuleEvent):void
    {
        var moduleInfo:IModuleInfo = event.currentTarget as IModuleInfo;
        moduleInfo.removeEventListener(ModuleEvent.READY, moduleInfo_readyHandler);
        moduleInfo.removeEventListener(ModuleEvent.ERROR, moduleInfo_errorHandler);

        const builderModule:IBuilderModule = event.module.factory.create() as IBuilderModule;
        if (builderModule)
        {
            const widgetType:WidgetType = new WidgetType(builderModule);
            _widgetTypeArr.push(widgetType);

            // Resolve the object
            //            FlexGlobals.topLevelApplication.registry.resolve(builderModule, ApplicationDomain.currentDomain);
            if (Log.isDebug())
            {
                LOG.debug('Module {0} is resolved', widgetType.name);
            }

            removeModuleInfo(event.module);
        }
    }

    private function removeModuleInfo(moduleInfo:IModuleInfo):void
    {
        const index:int = _moduleInfoArr.indexOf(moduleInfo);
        if (index > -1)
        {
            _moduleInfoArr.splice(index, 1);
            checkIfNoMoreModuleInfosLeft();
        }
    }

    private function checkIfNoMoreModuleInfosLeft():void
    {
        if (_moduleInfoArr.length === 0)
        {
            sortAndAssignWidgetTypes();
        }
    }

    private function sortAndAssignWidgetTypes():void
    {
        if (Log.isInfo())
        {
            LOG.info('All modules resolved');
        }

        _widgetTypeArr.sort(compareWidgetTypes);

        var widgetTypes:Array = _widgetTypeArr.filter(widgetTypeFilter);
        for each (var widgetType:WidgetType in widgetTypes)
        {
            WidgetTypeRegistryModel.getInstance().widgetTypeRegistry.addWidgetType(widgetType);
        }

        var layoutWidgetTypes:Array = _widgetTypeArr.filter(layoutWidgetTypeFilter);
        for each (var layoutWidgetType:WidgetType in layoutWidgetTypes)
        {
            WidgetTypeRegistryModel.getInstance().layoutWidgetTypeRegistry.addWidgetType(layoutWidgetType);
        }

        function widgetTypeFilter(item:WidgetType, index:int, source:Array):Boolean
        {
            return item.isManaged;
        }
        function layoutWidgetTypeFilter(item:WidgetType, index:int, source:Array):Boolean
        {
            return !item.isManaged;
        }

        dispatchEvent(new Event(Event.COMPLETE));
    }

    private function compareWidgetTypes(lhs:WidgetType, rhs:WidgetType):int
    {
        const lhsLabel:String = lhs.label.toLowerCase();
        const rhsLabel:String = rhs.label.toLowerCase();
        if (lhsLabel < rhsLabel)
        {
            return -1;
        }
        if (lhsLabel > rhsLabel)
        {
            return 1;
        }
        return 0;
    }

    private function moduleInfo_errorHandler(event:ModuleEvent):void
    {
        if (Log.isWarn())
        {
            LOG.warn('Module error: {0}', event.errorText);
        }

        var moduleInfo:IModuleInfo = event.currentTarget as IModuleInfo;
        moduleInfo.removeEventListener(ModuleEvent.ERROR, moduleInfo_errorHandler);
        moduleInfo.removeEventListener(ModuleEvent.READY, moduleInfo_readyHandler);

        removeModuleInfo(event.module);
        Model.instance.status = event.errorText;
        BuilderAlert.show(event.errorText,
                          ResourceManager.getInstance().getString('BuilderStrings', 'error'));
    }
}
}

Load modules from SWF bytes.

Load modules from SWF bytes.


package krewfw.core {

    import starling.display.DisplayObject;
    import starling.events.EnterFrameEvent;
    import starling.events.Event;

    import krewfw.core_internal.SceneServantActor;
    import krewfw.core_internal.StageLayer;
    import krewfw.core_internal.StuntAction;
    import krewfw.utils.as3.KrewTimeKeeper;

    //------------------------------------------------------------
    public class KrewScene extends KrewGameObject {

        private var _newActorPool:Array = new Array();
        private var _servantActor:SceneServantActor;  // to use multi tasker and so on
        private var _isTimeToExit:Boolean = false;
        private var _nextScene:KrewScene  = null;
        private var _hasDisposed:Boolean  = false;

        //------------------------------------------------------------
        public function KrewScene() {
            touchable = true;
            addEventListener(Event.ENTER_FRAME, _onEnterFrame);
        }

        public override function dispose():void {
            if (_hasDisposed) { return; }
            _hasDisposed = true;

            removeEventListener(Event.ENTER_FRAME, _onEnterFrame);
            sharedObj.layerManager.dispose();
            sharedObj.collisionSystem.removeAllGroups();
            sharedObj.resourceManager.purgeSceneScopeResources();
            _newActorPool = [];
            onDispose();
            super.dispose();
        }

        //------------------------------------------------------------
        // To compose your game scene, implements these handlers.
        //------------------------------------------------------------

        /**
         * 「ゲーム全体で常に保持しておきたいアセット」に追加するファイル名群を指定.
         * これは起動時に 1 回だけ呼ばれる起動処理専用の Scene で指定されることを想定している。
         * 現時点では、これによってグローバルに保持したアセットを解放するインタフェースは用意していない。
         *
         * Scene の getAdditionalGlobalAssets がオーバライドされていた場合、
         * Scene は遷移時に Global のアセット読み込みを先に行ってから
         * Scene スコープのアセット読み込みを行う。
         *
         * @see getRequiredAssets
         */
        public function getAdditionalGlobalAssets():Array {
            return [];
        }

        /**
         * このシーンで必要なアセットのファイルパス一覧を指定.
         * ここで指定したアセットによって確保されたリソースは、
         * シーン遷移時に自動的に解放される
         * @return Example:
         *

         * [
         *     "image/atlas_1.png",
         *     "image/atlas_1.xml",
         *     "bgm/bgm_1.mp3",
         *     "bmp_font/gothic.png",
         *     "bmp_font/gothic.fnt",
         *     "level_data/stage_1.json"
         * ]
         *

         */
        public function getRequiredAssets():Array {
            return [];
        }

        /**
         * ローディング画面を構成するための init.
         * この時点では KrewGameDirector で指定した大域リソースしか使えないので、
         * ここでセットする Actor はグローバルに保持しているリソースを使う必要がある
         */
        public function initLoadingView():void {
            krew.fwlog(' - default initLoadingView called.');
        }

        /**
         * getAdditionalGlobalAssets で指定したアセット群のロード中に呼ばれる。
         * アセットの指定があれば最低 1 回は呼ばれ、最後の呼び出しでは loadRatio = 1 となる
         */
        public function onLoadProgressGlobal(loadRatio:Number):void {
            krew.fwlog(' - - default onLoadProgressGlobal called: ' + loadRatio);
        }

        /**
         * getAdditionalGlobalAssets で指定したアセット群のロード完了時に呼ばれる。
         * （これの直後に getRequiredAssets で指定したアセットの読み込みが始まる）
         */
        public function onLoadCompleteGlobal():void {
            krew.fwlog(' - - - default onLoadCompleteGlobal called.');
        }

        /**
         * getRequiredAssets で指定したアセット群のロード中に呼ばれる。
         * アセットの指定があれば最低 1 回は呼ばれ、最後の呼び出しでは loadRatio = 1 となる
         */
        public function onLoadProgress(loadRatio:Number):void {
            krew.fwlog(' - - - - default onLoadProgress called: ' + loadRatio);
        }

        /**
         * getRequiredAssets で指定したアセット群のロード完了時に呼ばれる。
         * （これの直後に initAfterLoad が呼ばれる）
         */
        public function onLoadComplete():void {
            krew.fwlog(' - - - - - default onLoadComplete called.');
        }

        /**
         * 全てのアセットのロード完了後の本命のシーン初期化処理
         * ここから requiredAssets で指定したアセットが使える
         */
        public function initAfterLoad():void {
            krew.fwlog(' - - - - - - default initAfterLoad called.');
        }

        /**
         * exit() が呼ばれた際に遷移する次のシーンを返すよう override してほしい.
         * なお exit(specificScene) と引数にシーンを渡した場合はそのシーンが遷移先となり、
         * この関数は無視される
         */
        public function getDefaultNextScene():KrewScene {
            return null;
        }

        /**
         * レイヤー構造の定義
         * @return Example: ['back', 'front', 'ui']
         */
        public function getLayerList():Array {
            return ['l-back', 'l-front', 'l-ui'];
        }

        /**
         * 衝突判定を行うグループの定義
         * @return Example: Array of Array such as
         *

         * [
         *    ['myship' , ['enemy', 'en-shot', 'item']],
         *    ['myshot' , ['enemy']],
         *    ['enemy'  , []],
         *    ['en-shot', []],
         *    ['item'   , []]
         * ]
         *

         * こう書くと「c-myship と c-enemy グループ間」、
         * 「c-myship と c-item グループ間」などでヒットテストが行われるといった次第
         */
        public function getCollisionGroups():Array {
            return [];
        }

        //------------------------------------------------------------
        // Automatically called by framework
        //------------------------------------------------------------

        /** @private */
        public function startInitSceneSequence():void {
            sharedObj.layerManager.setUpLayers(this, getLayerList());
            sharedObj.collisionSystem.setUpGroups(getCollisionGroups());
            _setUpServantActor();

            initLoadingView();

            // load global assets, load local assets, and call scene init.
            _loadGlobalAssets(function():void {
                _loadSceneScopeAssets(function():void {
                    initAfterLoad();
                });
            });
        }

        private function _loadGlobalAssets(doneCallback:Function):void {
            // hook onLoadCompleteGlobal
            var _onLoadComplete:Function = function():void {
                _servantActor.sendMessage(KrewSystemEventType.COMPLETE_GLOBAL_ASSET_LOAD);
                onLoadCompleteGlobal();
                doneCallback();
            };
            if (getAdditionalGlobalAssets().length == 0) {
                _onLoadComplete();
                return;
            }

            // hook onLoadProgressGlobal
            var _onLoadProgressGlobal:Function = function(loadRatio:Number):void {
                _servantActor.sendMessage(
                    KrewSystemEventType.PROGRESS_GLOBAL_ASSET_LOAD, {loadRatio: loadRatio}
                );
                onLoadProgressGlobal(loadRatio);
            };

            // start loading assets
            sharedObj.resourceManager.loadGlobalResources(
                getAdditionalGlobalAssets(), _onLoadProgressGlobal, _onLoadComplete
            );
        }

        private function _loadSceneScopeAssets(doneCallback:Function):void {
            // hook onLoadComplete
            var _onLoadComplete:Function = function():void {
                _servantActor.sendMessage(KrewSystemEventType.COMPLETE_ASSET_LOAD);
                onLoadComplete();
                doneCallback();
            };
            if (getRequiredAssets().length == 0) {
                _onLoadComplete();
                return;
            }

            // hook onLoadProgress
            var _onLoadProgress:Function = function(loadRatio:Number):void {
                _servantActor.sendMessage(
                    KrewSystemEventType.PROGRESS_ASSET_LOAD, {loadRatio: loadRatio}
                );
                onLoadProgress(loadRatio);
            };

            // start loading assets
            sharedObj.resourceManager.loadResources(
                getRequiredAssets(), _onLoadProgress, _onLoadComplete
            );
        }

        private function _setUpServantActor():void {
            _servantActor = new SceneServantActor();
            setUpActor('_system_', _servantActor);
        }

        /**
         * @private
         * 新しい Actor を足してもらうよう Scene に頼む。
         * この関数は KrewActor によって呼ばれる。
         * 新しい Actor 達は既存の Actor 達の update の後に layer に足される。
         *
         * layer に足される前に新しい Actor が sharedObject などにアクセスできるように
         * するため、KrewActor.setUp() はこの段階で呼ばれる。
         */
        public function applyForNewActor(newActor:KrewActor, layerName:String=null):void {
            _newActorPool.push({
                actor: newActor,
                layer: layerName
            });

            newActor.setUp(
                sharedObj, applyForNewActor,
                getLayer(layerName), layerName
            );
        }

        private function _recruitNewActors():void {
            var count:int = 0;
            for each (var info:Object in _newActorPool) {
                ++count;
                setUpActor(info.layer, info.actor);
            }
            if (count > 0) {
                _newActorPool = [];
                //krew.fwlog(count + ' new actor joined');
            }
        }

        /** @private */
        public function addLayer(layer:StageLayer):void {
            layer.sharedObj = this.sharedObj;
            layer.applyForNewActor = this.applyForNewActor;
            addChild(layer);
        }

        /** @private */
        protected function addActor(layerName:String, actor:KrewActor,
                                    putOnDisplayList:Boolean=true):void {
            sharedObj.layerManager.addActor(layerName, actor, putOnDisplayList);
        }

        /** @private */
        protected function addChildToLayer(layerName:String, displayObj:DisplayObject):void {
            sharedObj.layerManager.addChild(layerName, displayObj);
        }

        /** @private */
        public function getNextScene():KrewScene {
            if (_nextScene != null) {
                return _nextScene;
            }
            return getDefaultNextScene();
        }

        //------------------------------------------------------------
        /**
         * Entry point of the game loop
         */
        private function _onEnterFrame(event:EnterFrameEvent):void {
            if (_hasDisposed) { return; }

            var passedTime:Number = KrewTimeKeeper.getReasonablePassedTime(event.passedTime);

            mainLoop(passedTime);
        }

        /**
         * Process one frame of game main loop.
         * It is called automatically by framework, but you can
         * manually call it for unit test of Actor's messaging.
         */
        public function mainLoop(passedTime:Number=1/60):void {
            if (_servantActor.isSystemActivated) {

                // update actors
                onUpdate(passedTime);
                sharedObj.layerManager.onUpdate(passedTime);
                _recruitNewActors();

                // collision detection
                sharedObj.collisionSystem.hitTest();
            }

            // broadcast messages
            sharedObj.notificationService.broadcastMessage();

            if (_isTimeToExit) {
                dispatchEvent(new Event(KrewSystemEventType.EXIT_SCENE));
            }
        }

        //------------------------------------------------------------
        // Your tools
        //------------------------------------------------------------
        protected function exit(nextScene:KrewScene=null):void {
            _isTimeToExit = true;
            _nextScene    = nextScene;
        }

        protected function act(action:StuntAction=null):StuntAction {
            return _servantActor.act(action);
        }

        protected function react():void {
            _servantActor.react();
        }

        public function addScheduledTask(timeout:Number, task:Function):void {
            _servantActor.addScheduledTask(timeout, task);
        }

        public function delay(timeout:Number, task:Function):void {
            _servantActor.addScheduledTask(timeout, task);
        }

        public function addPeriodicTask(interval:Number, task:Function):void {
            _servantActor.addPeriodicTask(interval, task);
        }

        public function cyclic(interval:Number, task:Function):void {
            _servantActor.addPeriodicTask(interval, task);
        }

        public function setUpActor(layerName:String, actor:KrewActor,
                                   putOnDisplayList:Boolean=true):void
        {
            if (layerName == null) { layerName = 'l-front'; }
            addActor(layerName, actor, putOnDisplayList);
        }
    }
}

package krewfw.core {

    import starling.display.DisplayObject;
    import starling.events.EnterFrameEvent;
    import starling.events.Event;

    import krewfw.core_internal.SceneServantActor;
    import krewfw.core_internal.StageLayer;
    import krewfw.core_internal.StuntAction;
    import krewfw.utils.as3.KrewTimeKeeper;

    //------------------------------------------------------------
    public class KrewScene extends KrewGameObject {

        private var _newActorPool:Array = new Array();
        private var _servantActor:SceneServantActor;  // to use multi tasker and so on
        private var _isTimeToExit:Boolean = false;
        private var _nextScene:KrewScene  = null;
        private var _hasDisposed:Boolean  = false;

        //------------------------------------------------------------
        public function KrewScene() {
            touchable = true;
            addEventListener(Event.ENTER_FRAME, _onEnterFrame);
        }

        public override function dispose():void {
            if (_hasDisposed) { return; }
            _hasDisposed = true;

            removeEventListener(Event.ENTER_FRAME, _onEnterFrame);
            sharedObj.layerManager.dispose();
            sharedObj.collisionSystem.removeAllGroups();
            sharedObj.resourceManager.purgeSceneScopeResources();
            _newActorPool = [];
            onDispose();
            super.dispose();
        }

        //------------------------------------------------------------
        // To compose your game scene, implements these handlers.
        //------------------------------------------------------------

        /**
         * 「ゲーム全体で常に保持しておきたいアセット」に追加するファイル名群を指定.
         * これは起動時に 1 回だけ呼ばれる起動処理専用の Scene で指定されることを想定している。
         * 現時点では、これによってグローバルに保持したアセットを解放するインタフェースは用意していない。
         *
         * Scene の getAdditionalGlobalAssets がオーバライドされていた場合、
         * Scene は遷移時に Global のアセット読み込みを先に行ってから
         * Scene スコープのアセット読み込みを行う。
         *
         * @see getRequiredAssets
         */
        public function getAdditionalGlobalAssets():Array {
            return [];
        }

        /**
         * このシーンで必要なアセットのファイルパス一覧を指定.
         * ここで指定したアセットによって確保されたリソースは、
         * シーン遷移時に自動的に解放される
         * @return Example:
         *

         * [
         *     "image/atlas_1.png",
         *     "image/atlas_1.xml",
         *     "bgm/bgm_1.mp3",
         *     "bmp_font/gothic.png",
         *     "bmp_font/gothic.fnt",
         *     "level_data/stage_1.json"
         * ]
         *

         */
        public function getRequiredAssets():Array {
            return [];
        }

        /**
         * ローディング画面を構成するための init.
         * この時点では KrewGameDirector で指定した大域リソースしか使えないので、
         * ここでセットする Actor はグローバルに保持しているリソースを使う必要がある
         */
        public function initLoadingView():void {
            krew.fwlog(' - default initLoadingView called.');
        }

        /**
         * getAdditionalGlobalAssets で指定したアセット群のロード中に呼ばれる。
         * アセットの指定があれば最低 1 回は呼ばれ、最後の呼び出しでは loadRatio = 1 となる
         */
        public function onLoadProgressGlobal(loadRatio:Number):void {
            krew.fwlog(' - - default onLoadProgressGlobal called: ' + loadRatio);
        }

        /**
         * getAdditionalGlobalAssets で指定したアセット群のロード完了時に呼ばれる。
         * （これの直後に getRequiredAssets で指定したアセットの読み込みが始まる）
         */
        public function onLoadCompleteGlobal():void {
            krew.fwlog(' - - - default onLoadCompleteGlobal called.');
        }

        /**
         * getRequiredAssets で指定したアセット群のロード中に呼ばれる。
         * アセットの指定があれば最低 1 回は呼ばれ、最後の呼び出しでは loadRatio = 1 となる
         */
        public function onLoadProgress(loadRatio:Number):void {
            krew.fwlog(' - - - - default onLoadProgress called: ' + loadRatio);
        }

        /**
         * getRequiredAssets で指定したアセット群のロード完了時に呼ばれる。
         * （これの直後に initAfterLoad が呼ばれる）
         */
        public function onLoadComplete():void {
            krew.fwlog(' - - - - - default onLoadComplete called.');
        }

        /**
         * 全てのアセットのロード完了後の本命のシーン初期化処理
         * ここから requiredAssets で指定したアセットが使える
         */
        public function initAfterLoad():void {
            krew.fwlog(' - - - - - - default initAfterLoad called.');
        }

        /**
         * exit() が呼ばれた際に遷移する次のシーンを返すよう override してほしい.
         * なお exit(specificScene) と引数にシーンを渡した場合はそのシーンが遷移先となり、
         * この関数は無視される
         */
        public function getDefaultNextScene():KrewScene {
            return null;
        }

        /**
         * レイヤー構造の定義
         * @return Example: ['back', 'front', 'ui']
         */
        public function getLayerList():Array {
            return ['l-back', 'l-front', 'l-ui'];
        }

        /**
         * 衝突判定を行うグループの定義
         * @return Example: Array of Array such as
         *

         * [
         *    ['myship' , ['enemy', 'en-shot', 'item']],
         *    ['myshot' , ['enemy']],
         *    ['enemy'  , []],
         *    ['en-shot', []],
         *    ['item'   , []]
         * ]
         *

         * こう書くと「c-myship と c-enemy グループ間」、
         * 「c-myship と c-item グループ間」などでヒットテストが行われるといった次第
         */
        public function getCollisionGroups():Array {
            return [];
        }

        //------------------------------------------------------------
        // Automatically called by framework
        //------------------------------------------------------------

        /** @private */
        public function startInitSceneSequence():void {
            sharedObj.layerManager.setUpLayers(this, getLayerList());
            sharedObj.collisionSystem.setUpGroups(getCollisionGroups());
            _setUpServantActor();

            initLoadingView();

            // load global assets, load local assets, and call scene init.
            _loadGlobalAssets(function():void {
                _loadSceneScopeAssets(function():void {
                    initAfterLoad();
                });
            });
        }

        private function _loadGlobalAssets(doneCallback:Function):void {
            // hook onLoadCompleteGlobal
            var _onLoadComplete:Function = function():void {
                _servantActor.sendMessage(KrewSystemEventType.COMPLETE_GLOBAL_ASSET_LOAD);
                onLoadCompleteGlobal();
                doneCallback();
            };
            if (getAdditionalGlobalAssets().length == 0) {
                _onLoadComplete();
                return;
            }

            // hook onLoadProgressGlobal
            var _onLoadProgressGlobal:Function = function(loadRatio:Number):void {
                _servantActor.sendMessage(
                    KrewSystemEventType.PROGRESS_GLOBAL_ASSET_LOAD, {loadRatio: loadRatio}
                );
                onLoadProgressGlobal(loadRatio);
            };

            // start loading assets
            sharedObj.resourceManager.loadGlobalResources(
                getAdditionalGlobalAssets(), _onLoadProgressGlobal, _onLoadComplete
            );
        }

        private function _loadSceneScopeAssets(doneCallback:Function):void {
            // hook onLoadComplete
            var _onLoadComplete:Function = function():void {
                _servantActor.sendMessage(KrewSystemEventType.COMPLETE_ASSET_LOAD);
                onLoadComplete();
                doneCallback();
            };
            if (getRequiredAssets().length == 0) {
                _onLoadComplete();
                return;
            }

            // hook onLoadProgress
            var _onLoadProgress:Function = function(loadRatio:Number):void {
                _servantActor.sendMessage(
                    KrewSystemEventType.PROGRESS_ASSET_LOAD, {loadRatio: loadRatio}
                );
                onLoadProgress(loadRatio);
            };

            // start loading assets
            sharedObj.resourceManager.loadResources(
                getRequiredAssets(), _onLoadProgress, _onLoadComplete
            );
        }

        private function _setUpServantActor():void {
            _servantActor = new SceneServantActor();
            setUpActor('_system_', _servantActor);
        }

        /**
         * @private
         * 新しい Actor を足してもらうよう Scene に頼む。
         * この関数は KrewActor によって呼ばれる。
         * 新しい Actor 達は既存の Actor 達の update の後に layer に足される。
         *
         * layer に足される前に新しい Actor が sharedObject などにアクセスできるように
         * するため、KrewActor.setUp() はこの段階で呼ばれる。
         */
        public function applyForNewActor(newActor:KrewActor, layerName:String=null):void {
            _newActorPool.push({
                actor: newActor,
                layer: layerName
            });

            newActor.setUp(
                sharedObj, applyForNewActor,
                getLayer(layerName), layerName
            );
        }

        private function _recruitNewActors():void {
            var count:int = 0;
            for each (var info:Object in _newActorPool) {
                ++count;
                setUpActor(info.layer, info.actor);
            }
            if (count > 0) {
                _newActorPool = [];
                //krew.fwlog(count + ' new actor joined');
            }
        }

        /** @private */
        public function addLayer(layer:StageLayer):void {
            layer.sharedObj = this.sharedObj;
            layer.applyForNewActor = this.applyForNewActor;
            addChild(layer);
        }

        /** @private */
        protected function addActor(layerName:String, actor:KrewActor,
                                    putOnDisplayList:Boolean=true):void {
            sharedObj.layerManager.addActor(layerName, actor, putOnDisplayList);
        }

        /** @private */
        protected function addChildToLayer(layerName:String, displayObj:DisplayObject):void {
            sharedObj.layerManager.addChild(layerName, displayObj);
        }

        /** @private */
        public function getNextScene():KrewScene {
            if (_nextScene != null) {
                return _nextScene;
            }
            return getDefaultNextScene();
        }

        //------------------------------------------------------------
        /**
         * Entry point of the game loop
         */
        private function _onEnterFrame(event:EnterFrameEvent):void {
            if (_hasDisposed) { return; }

            var passedTime:Number = KrewTimeKeeper.getReasonablePassedTime(event.passedTime);

            mainLoop(passedTime);
        }

        /**
         * Process one frame of game main loop.
         * It is called automatically by framework, but you can
         * manually call it for unit test of Actor's messaging.
         */
        public function mainLoop(passedTime:Number=1/60):void {
            if (_servantActor.isSystemActivated) {

                // update actors
                onUpdate(passedTime);
                sharedObj.layerManager.onUpdate(passedTime);
                _recruitNewActors();

                // collision detection
                sharedObj.collisionSystem.hitTest();
            }

            // broadcast messages
            sharedObj.notificationService.broadcastMessage();

            if (_isTimeToExit) {
                dispatchEvent(new Event(KrewSystemEventType.EXIT_SCENE));
            }
        }

        //------------------------------------------------------------
        // Your tools
        //------------------------------------------------------------
        protected function exit(nextScene:KrewScene=null):void {
            _isTimeToExit = true;
            _nextScene    = nextScene;
        }

        protected function act(action:StuntAction=null):StuntAction {
            return _servantActor.act(action);
        }

        protected function react():void {
            _servantActor.react();
        }

        public function addScheduledTask(timeout:Number, task:Function):void {
            _servantActor.addScheduledTask(timeout, task);
        }

        public function delayed(timeout:Number, task:Function):void {
            _servantActor.addScheduledTask(timeout, task);
        }

        public function addPeriodicTask(interval:Number, task:Function):void {
            _servantActor.addPeriodicTask(interval, task);
        }

        public function cyclic(interval:Number, task:Function):void {
            _servantActor.addPeriodicTask(interval, task);
        }

        public function setUpActor(layerName:String, actor:KrewActor,
                                   putOnDisplayList:Boolean=true):void
        {
            if (layerName == null) { layerName = 'l-front'; }
            addActor(layerName, actor, putOnDisplayList);
        }
    }
}

Rename delay to delayed

Rename delay to delayed


/*
 * Copyright 2008 Adobe Systems Inc., 2008 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Contributor(s):
 *   Zwetan Kjukov <zwetan@gmail.com>.
 */

package com.google.analytics
{
    import com.google.analytics.utils.Version;

    public class API
    {

        /**
        * version of Google Analytics AS3 API
        *
        * note:
        * each components share the same code base and so the same version
        */
        public static var version:Version = new Version();
        include "version.properties"
        version.revision = "$Rev$ ".split( " " )[1];

    }
}

/*
 * Copyright 2008 Adobe Systems Inc., 2008 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Contributor(s):
 *   Zwetan Kjukov <zwetan@gmail.com>.
 */

package com.google.analytics
{
    import core.version;

    public class API
    {

        /**
        * version of Google Analytics AS3 API
        *
        * note:
        * each components share the same code base and so the same version
        */
        public static var version:core.version = new core.version();
        include "version.properties"
        version.revision = "$Rev$ ".split( " " )[1];

    }
}

replace utils Version class by uri.version class


// Copyright: Hiroshi Ichikawa <http://gimite.net/en/>
// License: New BSD License
// Reference: http://dev.w3.org/html5/websockets/
// Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-31

package {

import flash.display.*;
import flash.events.*;
import flash.external.*;
import flash.net.*;
import flash.system.*;
import flash.utils.*;
import mx.core.*;
import mx.controls.*;
import mx.events.*;
import mx.utils.*;
import com.adobe.net.proxies.RFC2817Socket;
import com.gsolo.encryption.MD5;

[Event(name="message", type="WebSocketMessageEvent")]
[Event(name="open", type="flash.events.Event")]
[Event(name="close", type="flash.events.Event")]
[Event(name="error", type="flash.events.Event")]
[Event(name="stateChange", type="WebSocketStateEvent")]
public class WebSocket extends EventDispatcher {

  private static var CONNECTING:int = 0;
  private static var OPEN:int = 1;
  private static var CLOSING:int = 2;
  private static var CLOSED:int = 3;

  private var socket:RFC2817Socket;
  private var main:WebSocketMain;
  private var url:String;
  private var scheme:String;
  private var host:String;
  private var port:uint;
  private var path:String;
  private var origin:String;
  private var protocol:String;
  private var buffer:ByteArray = new ByteArray();
  private var headerState:int = 0;
  private var readyState:int = CONNECTING;
  private var bufferedAmount:int = 0;
  private var headers:String;
  private var noiseChars:Array;
  private var expectedDigest:String;

  public function WebSocket(
      main:WebSocketMain, url:String, protocol:String,
      proxyHost:String = null, proxyPort:int = 0,
      headers:String = null) {
    this.main = main;
    initNoiseChars();
    this.url = url;
    var m:Array = url.match(/^(\w+):\/\/([^\/:]+)(:(\d+))?(\/.*)?$/);
    if (!m) main.fatal("SYNTAX_ERR: invalid url: " + url);
    this.scheme = m[1];
    this.host = m[2];
    this.port = parseInt(m[4] || "80");
    this.path = m[5] || "/";
    this.origin = main.getOrigin();
    this.protocol = protocol;
    // if present and not the empty string, headers MUST end with \r\n
    // headers should be zero or more complete lines, for example
    // "Header1: xxx\r\nHeader2: yyyy\r\n"
    this.headers = headers;

    socket = new RFC2817Socket();

    // if no proxy information is supplied, it acts like a normal Socket
    // @see RFC2817Socket::connect
    if (proxyHost != null && proxyPort != 0){
      socket.setProxyInfo(proxyHost, proxyPort);
    }

    socket.addEventListener(Event.CLOSE, onSocketClose);
    socket.addEventListener(Event.CONNECT, onSocketConnect);
    socket.addEventListener(IOErrorEvent.IO_ERROR, onSocketIoError);
    socket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSocketSecurityError);
    socket.addEventListener(ProgressEvent.SOCKET_DATA, onSocketData);
    socket.connect(host, port);
  }

  public function send(data:String):int {
    if (readyState == OPEN) {
      socket.writeByte(0x00);
      socket.writeUTFBytes(decodeURIComponent(data));
      socket.writeByte(0xff);
      socket.flush();
      main.log("sent: " + data);
      return -1;
    } else if (readyState == CLOSED) {
      var bytes:ByteArray = new ByteArray();
      bytes.writeUTFBytes(decodeURIComponent(data));
      bufferedAmount += bytes.length; // not sure whether it should include \x00 and \xff
      // We use return value to let caller know bufferedAmount because we cannot fire
      // stateChange event here which causes weird error:
      // > You are trying to call recursively into the Flash Player which is not allowed.
      return bufferedAmount;
    } else {
      main.fatal("INVALID_STATE_ERR: invalid state");
      return 0;
    }
  }

  public function close():void {
    main.log("close");
    try {
      socket.close();
    } catch (ex:Error) { }
    readyState = CLOSED;
    // We don't fire any events here because it causes weird error:
    // > You are trying to call recursively into the Flash Player which is not allowed.
    // We do something equivalent in JavaScript WebSocket#close instead.
  }

  public function getReadyState():int {
    return readyState;
  }

  public function getBufferedAmount():int {
    return bufferedAmount;
  }

  private function onSocketConnect(event:Event):void {
    main.log("connected");
    var hostValue:String = host + (port == 80 ? "" : ":" + port);
    var cookie:String = "";
    if (main.getCallerHost() == host) {
      cookie = ExternalInterface.call("function(){return document.cookie}");
    }
    var key1:String = generateKey();
    var key2:String = generateKey();
    var key3:String = generateKey3();
    expectedDigest = getSecurityDigest(key1, key2, key3);
    var opt:String = "";
    if (protocol) opt += "WebSocket-Protocol: " + protocol + "\r\n";
    // if caller passes additional headers they must end with "\r\n"
    if (headers) opt += headers;

    var req:String = StringUtil.substitute(
      "GET {0} HTTP/1.1\r\n" +
      "Upgrade: WebSocket\r\n" +
      "Connection: Upgrade\r\n" +
      "Host: {1}\r\n" +
      "Origin: {2}\r\n" +
      "Cookie: {3}\r\n" +
      "Sec-WebSocket-Key1: {4}\r\n" +
      "Sec-WebSocket-Key2: {5}\r\n" +
      "{6}" +
      "\r\n",
      path, hostValue, origin, cookie, key1, key2, opt);
    main.log("request header:\n" + req);
    socket.writeUTFBytes(req);
    main.log("sent key3: " + key3);
    main.log("expected digest: " + expectedDigest);
    writeBytes(key3);
    socket.flush();
  }

  private function onSocketClose(event:Event):void {
    main.log("closed");
    readyState = CLOSED;
    notifyStateChange();
    dispatchEvent(new Event("close"));
  }

  private function onSocketIoError(event:IOErrorEvent):void {
    var message:String;
    if (readyState == CONNECTING) {
      message = "cannot connect to Web Socket server at " + url + " (IoError)";
    } else {
      message = "error communicating with Web Socket server at " + url + " (IoError)";
    }
    onError(message);
  }

  private function onSocketSecurityError(event:SecurityErrorEvent):void {
    var message:String;
    if (readyState == CONNECTING) {
      message =
          "cannot connect to Web Socket server at " + url + " (SecurityError)\n" +
          "make sure the server is running and Flash socket policy file is correctly placed";
    } else {
      message = "error communicating with Web Socket server at " + url + " (SecurityError)";
    }
    onError(message);
  }

  private function onError(message:String):void {
    var state:int = readyState;
    if (state == CLOSED) return;
    main.error(message);
    close();
    notifyStateChange();
    dispatchEvent(new Event(state == CONNECTING ? "close" : "error"));
  }

  private function onSocketData(event:ProgressEvent):void {
    var pos:int = buffer.length;
    socket.readBytes(buffer, pos);
    for (; pos < buffer.length; ++pos) {
      if (headerState < 4) {
        // try to find "\r\n\r\n"
        if ((headerState == 0 || headerState == 2) && buffer[pos] == 0x0d) {
          ++headerState;
        } else if ((headerState == 1 || headerState == 3) && buffer[pos] == 0x0a) {
          ++headerState;
        } else {
          headerState = 0;
        }
        if (headerState == 4) {
          var headerStr:String = buffer.readUTFBytes(pos + 1);
          main.log("response header:\n" + headerStr);
          if (!validateHeader(headerStr)) return;
          makeBufferCompact();
          pos = -1;
        }
      } else if (headerState == 4) {
        var replyDigest:String = readBytes(buffer, 16);
        main.log("reply digest: " + replyDigest);
        if (replyDigest != expectedDigest) {
          onError("digest doesn't match: " + replyDigest + " != " + expectedDigest);
          return;
        }
        headerState = 5;
        makeBufferCompact();
        pos = -1;
        readyState = OPEN;
        notifyStateChange();
        dispatchEvent(new Event("open"));
      } else {
        if (buffer[pos] == 0xff) {
          if (buffer.readByte() != 0x00) {
            onError("data must start with \\x00");
            return;
          }
          var data:String = buffer.readUTFBytes(pos - 1);
          main.log("received: " + data);
          dispatchEvent(new WebSocketMessageEvent("message", encodeURIComponent(data)));
          buffer.readByte();
          makeBufferCompact();
          pos = -1;
        }
      }
    }
  }

  private function validateHeader(headerStr:String):Boolean {
    var lines:Array = headerStr.split(/\r\n/);
    if (!lines[0].match(/^HTTP\/1.1 101 /)) {
      onError("bad response: " + lines[0]);
      return false;
    }
    var header:Object = {};
    for (var i:int = 1; i < lines.length; ++i) {
      if (lines[i].length == 0) continue;
      var m:Array = lines[i].match(/^(\S+): (.*)$/);
      if (!m) {
        onError("failed to parse response header line: " + lines[i]);
        return false;
      }
      header[m[1]] = m[2];
    }
    if (header["Upgrade"] != "WebSocket") {
      onError("invalid Upgrade: " + header["Upgrade"]);
      return false;
    }
    if (header["Connection"] != "Upgrade") {
      onError("invalid Connection: " + header["Connection"]);
      return false;
    }
    var resOrigin:String = header["Sec-WebSocket-Origin"].toLowerCase();
    if (resOrigin != origin) {
      onError("origin doesn't match: '" + resOrigin + "' != '" + origin + "'");
      return false;
    }
    if (protocol && header["Sec-WebSocket-Protocol"] != protocol) {
      onError("protocol doesn't match: '" +
        header["WebSocket-Protocol"] + "' != '" + protocol + "'");
      return false;
    }
    return true;
  }

  private function makeBufferCompact():void {
    if (buffer.position == 0) return;
    var nextBuffer:ByteArray = new ByteArray();
    buffer.readBytes(nextBuffer);
    buffer = nextBuffer;
  }

  private function notifyStateChange():void {
    dispatchEvent(new WebSocketStateEvent("stateChange", readyState, bufferedAmount));
  }

  private function initNoiseChars():void {
    noiseChars = new Array();
    for (var i:int = 0x21; i <= 0x2f; ++i) {
      noiseChars.push(String.fromCharCode(i));
    }
    for (var j:int = 0x3a; j <= 0x7a; ++j) {
      noiseChars.push(String.fromCharCode(j));
    }
  }

  private function generateKey():String {
    var spaces:uint = randomInt(1, 12);
    var max:uint = uint.MAX_VALUE / spaces;
    var number:uint = randomInt(0, max);
    var key:String = (number * spaces).toString();
    var noises:int = randomInt(1, 12);
    var pos:int;
    for (var i:int = 0; i < noises; ++i) {
      var char:String = noiseChars[randomInt(0, noiseChars.length - 1)];
      pos = randomInt(0, key.length);
      key = key.substr(0, pos) + char + key.substr(pos);
    }
    for (var j:int = 0; j < spaces; ++j) {
      pos = randomInt(1, key.length - 1);
      key = key.substr(0, pos) + " " + key.substr(pos);
    }
    return key;
  }

  private function generateKey3():String {
    var key3:String = "";
    for (var i:int = 0; i < 8; ++i) {
      key3 += String.fromCharCode(randomInt(0, 255));
    }
    return key3;
  }

  private function getSecurityDigest(key1:String, key2:String, key3:String):String {
    var bytes1:String = keyToBytes(key1);
    var bytes2:String = keyToBytes(key2);
    return MD5.rstr_md5(bytes1 + bytes2 + key3);
  }

  private function keyToBytes(key:String):String {
    var keyNum:uint = parseInt(key.replace(/[^\d]/g, ""));
    var spaces:uint = 0;
    for (var i:int = 0; i < key.length; ++i) {
      if (key.charAt(i) == " ") ++spaces;
    }
    var resultNum:uint = keyNum / spaces;
    var bytes:String = "";
    for (var j:int = 3; j >= 0; --j) {
      bytes += String.fromCharCode((resultNum >> (j * 8)) & 0xff);
    }
    return bytes;
  }

  private function writeBytes(bytes:String):void {
    for (var i:int = 0; i < bytes.length; ++i) {
      socket.writeByte(bytes.charCodeAt(i));
    }
  }

  private function readBytes(buffer:ByteArray, numBytes:int):String {
    var bytes:String = "";
    for (var i:int = 0; i < numBytes; ++i) {
      // & 0xff is to make \x80-\xff positive number.
      bytes += String.fromCharCode(buffer.readByte() & 0xff);
    }
    return bytes;
  }

  private function randomInt(min:uint, max:uint):uint {
    return min + Math.floor(Math.random() * (Number(max) - min + 1));
  }

  // for debug
  private function dumpBytes(bytes:String):void {
    var output:String = "";
    for (var i:int = 0; i < bytes.length; ++i) {
      output += bytes.charCodeAt(i).toString() + ", ";
    }
    main.log(output);
  }

}

}

// Copyright: Hiroshi Ichikawa <http://gimite.net/en/>
// License: New BSD License
// Reference: http://dev.w3.org/html5/websockets/
// Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-31

package {

import flash.display.*;
import flash.events.*;
import flash.external.*;
import flash.net.*;
import flash.system.*;
import flash.utils.*;
import mx.core.*;
import mx.controls.*;
import mx.events.*;
import mx.utils.*;
import com.adobe.net.proxies.RFC2817Socket;
import com.hurlant.crypto.tls.TLSSocket;
import com.hurlant.crypto.tls.TLSConfig;
import com.hurlant.crypto.tls.TLSEngine;
import com.hurlant.crypto.tls.TLSSecurityParameters;
import com.gsolo.encryption.MD5;

[Event(name="message", type="WebSocketMessageEvent")]
[Event(name="open", type="flash.events.Event")]
[Event(name="close", type="flash.events.Event")]
[Event(name="error", type="flash.events.Event")]
[Event(name="stateChange", type="WebSocketStateEvent")]
public class WebSocket extends EventDispatcher {

  private static var CONNECTING:int = 0;
  private static var OPEN:int = 1;
  private static var CLOSING:int = 2;
  private static var CLOSED:int = 3;

  //private var rawSocket:RFC2817Socket;
  private var rawSocket:Socket;
  private var tlsSocket:TLSSocket;
  private var tlsConfig:TLSConfig;
  private var socket:Socket;
  private var main:WebSocketMain;
  private var url:String;
  private var scheme:String;
  private var host:String;
  private var port:uint;
  private var path:String;
  private var origin:String;
  private var protocol:String;
  private var buffer:ByteArray = new ByteArray();
  private var headerState:int = 0;
  private var readyState:int = CONNECTING;
  private var bufferedAmount:int = 0;
  private var headers:String;
  private var noiseChars:Array;
  private var expectedDigest:String;

  public function WebSocket(
      main:WebSocketMain, url:String, protocol:String,
      proxyHost:String = null, proxyPort:int = 0,
      headers:String = null) {
    this.main = main;
    initNoiseChars();
    this.url = url;
    var m:Array = url.match(/^(\w+):\/\/([^\/:]+)(:(\d+))?(\/.*)?$/);
    if (!m) main.fatal("SYNTAX_ERR: invalid url: " + url);
    this.scheme = m[1];
    this.host = m[2];
    this.port = parseInt(m[4] || "80");
    this.path = m[5] || "/";
    this.origin = main.getOrigin();
    this.protocol = protocol;
    // if present and not the empty string, headers MUST end with \r\n
    // headers should be zero or more complete lines, for example
    // "Header1: xxx\r\nHeader2: yyyy\r\n"
    this.headers = headers;

    /*
    socket = new RFC2817Socket();

    // if no proxy information is supplied, it acts like a normal Socket
    // @see RFC2817Socket::connect
    if (proxyHost != null && proxyPort != 0){
      socket.setProxyInfo(proxyHost, proxyPort);
    }
    */

    ExternalInterface.call("console.log", "[WebSocket] scheme: " + scheme);
    rawSocket = new Socket();

    rawSocket.addEventListener(Event.CLOSE, onSocketClose);
    rawSocket.addEventListener(Event.CONNECT, onSocketConnect);
    rawSocket.addEventListener(IOErrorEvent.IO_ERROR, onSocketIoError);
    rawSocket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSocketSecurityError);
    if (scheme == "wss") {
        tlsConfig= new TLSConfig(TLSEngine.CLIENT,
            null, null, null, null, null,
            TLSSecurityParameters.PROTOCOL_VERSION);
        tlsConfig.trustSelfSignedCertificates = true;
        tlsConfig.ignoreCommonNameMismatch = true;

        tlsSocket = new TLSSocket();
        tlsSocket.addEventListener(ProgressEvent.SOCKET_DATA, onSocketData);
        socket = (tlsSocket as Socket);
    } else {
        rawSocket.addEventListener(ProgressEvent.SOCKET_DATA, onSocketData);
        socket = (rawSocket as Socket);
    }
    rawSocket.connect(host, port);
  }

  public function send(data:String):int {
    if (readyState == OPEN) {
      socket.writeByte(0x00);
      socket.writeUTFBytes(decodeURIComponent(data));
      socket.writeByte(0xff);
      socket.flush();
      main.log("sent: " + data);
      return -1;
    } else if (readyState == CLOSED) {
      var bytes:ByteArray = new ByteArray();
      bytes.writeUTFBytes(decodeURIComponent(data));
      bufferedAmount += bytes.length; // not sure whether it should include \x00 and \xff
      // We use return value to let caller know bufferedAmount because we cannot fire
      // stateChange event here which causes weird error:
      // > You are trying to call recursively into the Flash Player which is not allowed.
      return bufferedAmount;
    } else {
      main.fatal("INVALID_STATE_ERR: invalid state");
      return 0;
    }
  }

  public function close():void {
    main.log("close");
    try {
      socket.close();
    } catch (ex:Error) { }
    readyState = CLOSED;
    // We don't fire any events here because it causes weird error:
    // > You are trying to call recursively into the Flash Player which is not allowed.
    // We do something equivalent in JavaScript WebSocket#close instead.
  }

  public function getReadyState():int {
    return readyState;
  }

  public function getBufferedAmount():int {
    return bufferedAmount;
  }

  private function onSocketConnect(event:Event):void {
    main.log("connected");

    if (scheme == "wss") {
        ExternalInterface.call("console.log", "[WebSocket] starting SSL/TLS");
        tlsSocket.startTLS(rawSocket, host, tlsConfig);
    }

    var hostValue:String = host + (port == 80 ? "" : ":" + port);
    var cookie:String = "";
    if (main.getCallerHost() == host) {
      cookie = ExternalInterface.call("function(){return document.cookie}");
    }
    var key1:String = generateKey();
    var key2:String = generateKey();
    var key3:String = generateKey3();
    expectedDigest = getSecurityDigest(key1, key2, key3);
    var opt:String = "";
    if (protocol) opt += "WebSocket-Protocol: " + protocol + "\r\n";
    // if caller passes additional headers they must end with "\r\n"
    if (headers) opt += headers;

    var req:String = StringUtil.substitute(
      "GET {0} HTTP/1.1\r\n" +
      "Upgrade: WebSocket\r\n" +
      "Connection: Upgrade\r\n" +
      "Host: {1}\r\n" +
      "Origin: {2}\r\n" +
      "Cookie: {3}\r\n" +
      "Sec-WebSocket-Key1: {4}\r\n" +
      "Sec-WebSocket-Key2: {5}\r\n" +
      "{6}" +
      "\r\n",
      path, hostValue, origin, cookie, key1, key2, opt);
    main.log("request header:\n" + req);
    socket.writeUTFBytes(req);
    main.log("sent key3: " + key3);
    main.log("expected digest: " + expectedDigest);
    writeBytes(key3);
    socket.flush();
  }

  private function onSocketClose(event:Event):void {
    main.log("closed");
    readyState = CLOSED;
    notifyStateChange();
    dispatchEvent(new Event("close"));
  }

  private function onSocketIoError(event:IOErrorEvent):void {
    var message:String;
    if (readyState == CONNECTING) {
      message = "cannot connect to Web Socket server at " + url + " (IoError)";
    } else {
      message = "error communicating with Web Socket server at " + url + " (IoError)";
    }
    onError(message);
  }

  private function onSocketSecurityError(event:SecurityErrorEvent):void {
    var message:String;
    if (readyState == CONNECTING) {
      message =
          "cannot connect to Web Socket server at " + url + " (SecurityError)\n" +
          "make sure the server is running and Flash socket policy file is correctly placed";
    } else {
      message = "error communicating with Web Socket server at " + url + " (SecurityError)";
    }
    onError(message);
  }

  private function onError(message:String):void {
    var state:int = readyState;
    if (state == CLOSED) return;
    main.error(message);
    close();
    notifyStateChange();
    dispatchEvent(new Event(state == CONNECTING ? "close" : "error"));
  }

  private function onSocketData(event:ProgressEvent):void {
    var pos:int = buffer.length;
    socket.readBytes(buffer, pos);
    for (; pos < buffer.length; ++pos) {
      if (headerState < 4) {
        // try to find "\r\n\r\n"
        if ((headerState == 0 || headerState == 2) && buffer[pos] == 0x0d) {
          ++headerState;
        } else if ((headerState == 1 || headerState == 3) && buffer[pos] == 0x0a) {
          ++headerState;
        } else {
          headerState = 0;
        }
        if (headerState == 4) {
          var headerStr:String = buffer.readUTFBytes(pos + 1);
          main.log("response header:\n" + headerStr);
          if (!validateHeader(headerStr)) return;
          makeBufferCompact();
          pos = -1;
        }
      } else if (headerState == 4) {
        var replyDigest:String = readBytes(buffer, 16);
        main.log("reply digest: " + replyDigest);
        if (replyDigest != expectedDigest) {
          onError("digest doesn't match: " + replyDigest + " != " + expectedDigest);
          return;
        }
        headerState = 5;
        makeBufferCompact();
        pos = -1;
        readyState = OPEN;
        notifyStateChange();
        dispatchEvent(new Event("open"));
      } else {
        if (buffer[pos] == 0xff) {
          if (buffer.readByte() != 0x00) {
            onError("data must start with \\x00");
            return;
          }
          var data:String = buffer.readUTFBytes(pos - 1);
          main.log("received: " + data);
          dispatchEvent(new WebSocketMessageEvent("message", encodeURIComponent(data)));
          buffer.readByte();
          makeBufferCompact();
          pos = -1;
        }
      }
    }
  }

  private function validateHeader(headerStr:String):Boolean {
    var lines:Array = headerStr.split(/\r\n/);
    if (!lines[0].match(/^HTTP\/1.1 101 /)) {
      onError("bad response: " + lines[0]);
      return false;
    }
    var header:Object = {};
    for (var i:int = 1; i < lines.length; ++i) {
      if (lines[i].length == 0) continue;
      var m:Array = lines[i].match(/^(\S+): (.*)$/);
      if (!m) {
        onError("failed to parse response header line: " + lines[i]);
        return false;
      }
      header[m[1]] = m[2];
    }
    if (header["Upgrade"] != "WebSocket") {
      onError("invalid Upgrade: " + header["Upgrade"]);
      return false;
    }
    if (header["Connection"] != "Upgrade") {
      onError("invalid Connection: " + header["Connection"]);
      return false;
    }
    var resOrigin:String = header["Sec-WebSocket-Origin"].toLowerCase();
    if (resOrigin != origin) {
      onError("origin doesn't match: '" + resOrigin + "' != '" + origin + "'");
      return false;
    }
    if (protocol && header["Sec-WebSocket-Protocol"] != protocol) {
      onError("protocol doesn't match: '" +
        header["WebSocket-Protocol"] + "' != '" + protocol + "'");
      return false;
    }
    return true;
  }

  private function makeBufferCompact():void {
    if (buffer.position == 0) return;
    var nextBuffer:ByteArray = new ByteArray();
    buffer.readBytes(nextBuffer);
    buffer = nextBuffer;
  }

  private function notifyStateChange():void {
    dispatchEvent(new WebSocketStateEvent("stateChange", readyState, bufferedAmount));
  }

  private function initNoiseChars():void {
    noiseChars = new Array();
    for (var i:int = 0x21; i <= 0x2f; ++i) {
      noiseChars.push(String.fromCharCode(i));
    }
    for (var j:int = 0x3a; j <= 0x7a; ++j) {
      noiseChars.push(String.fromCharCode(j));
    }
  }

  private function generateKey():String {
    var spaces:uint = randomInt(1, 12);
    var max:uint = uint.MAX_VALUE / spaces;
    var number:uint = randomInt(0, max);
    var key:String = (number * spaces).toString();
    var noises:int = randomInt(1, 12);
    var pos:int;
    for (var i:int = 0; i < noises; ++i) {
      var char:String = noiseChars[randomInt(0, noiseChars.length - 1)];
      pos = randomInt(0, key.length);
      key = key.substr(0, pos) + char + key.substr(pos);
    }
    for (var j:int = 0; j < spaces; ++j) {
      pos = randomInt(1, key.length - 1);
      key = key.substr(0, pos) + " " + key.substr(pos);
    }
    return key;
  }

  private function generateKey3():String {
    var key3:String = "";
    for (var i:int = 0; i < 8; ++i) {
      key3 += String.fromCharCode(randomInt(0, 255));
    }
    return key3;
  }

  private function getSecurityDigest(key1:String, key2:String, key3:String):String {
    var bytes1:String = keyToBytes(key1);
    var bytes2:String = keyToBytes(key2);
    return MD5.rstr_md5(bytes1 + bytes2 + key3);
  }

  private function keyToBytes(key:String):String {
    var keyNum:uint = parseInt(key.replace(/[^\d]/g, ""));
    var spaces:uint = 0;
    for (var i:int = 0; i < key.length; ++i) {
      if (key.charAt(i) == " ") ++spaces;
    }
    var resultNum:uint = keyNum / spaces;
    var bytes:String = "";
    for (var j:int = 3; j >= 0; --j) {
      bytes += String.fromCharCode((resultNum >> (j * 8)) & 0xff);
    }
    return bytes;
  }

  private function writeBytes(bytes:String):void {
    for (var i:int = 0; i < bytes.length; ++i) {
      socket.writeByte(bytes.charCodeAt(i));
    }
  }

  private function readBytes(buffer:ByteArray, numBytes:int):String {
    var bytes:String = "";
    for (var i:int = 0; i < numBytes; ++i) {
      // & 0xff is to make \x80-\xff positive number.
      bytes += String.fromCharCode(buffer.readByte() & 0xff);
    }
    return bytes;
  }

  private function randomInt(min:uint, max:uint):uint {
    return min + Math.floor(Math.random() * (Number(max) - min + 1));
  }

  // for debug
  private function dumpBytes(bytes:String):void {
    var output:String = "";
    for (var i:int = 0; i < bytes.length; ++i) {
      output += bytes.charCodeAt(i).toString() + ", ";
    }
    main.log(output);
  }

}

}

Add wss:// (SSL/TLS) support using as3crypto_patched.

Warning: RFC2817 proxy support interferes with the SSL Socket
(hurlant) so I've disabled RFC2817 proxy support.


/*
 * Copyright 2014 Mozilla Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package flash.geom
{
  import flash.geom.Point;

  public class Rectangle
  {
    public var x:Number;
    public var y:Number;
    public var width:Number;
    public var height:Number;

    public function Rectangle(x:Number = 0, y:Number = 0, width:Number = 0, height:Number = 0) {
      this.x = x;
      this.y = y;
      this.width = width;
      this.height = height;
    }

    public function get left():Number {
      return x;
    }
    public function set left(value:Number):void {
      width += x - value
      x = value;
    }

    public function get right():Number {
      return x + width;
    }
    public function set right(value:Number):void {
      width = value - x;
    }

    public function get top():Number {
      return y;
    }
    public function set top(value:Number):void {
      height += y - value;
      y = value;
    }

    public function get bottom():Number {
      return y + height;
    }
    public function set bottom(value:Number):void {
      height = value - y;
    }

    public function get topLeft():Point {
      return new Point(left, top);
    }
    public function set topLeft(value:Point):void {
      top = value.y;
      left = value.x;
    }

    public function get bottomRight():Point {
      return new Point(right, bottom);
    }
    public function set bottomRight(value:Point):void {
      bottom = value.y;
      right = value.x;
    }

    public function get size():Point {
      return new Point(width, height);
    }
    public function set size(value:Point):void {
      width = value.x;
      height = value.y;
    }

    public function clone():Rectangle {
      return new Rectangle(x, y, width, height);
    }

    public function isEmpty():Boolean {
      return width <= 0 || height <= 0;
    }

    public function setEmpty():void {
      x = 0;
      y = 0;
      width = 0;
      height = 0;
    }

    public function inflate(dx:Number, dy:Number):void {
      x -= dx;
      y -= dy;
      width += (dx * 2);
      height += (dy * 2);
    }

    public function inflatePoint(point:Point):void {
      inflate(point.x, point.y);
    }

    public function offset(dx:Number, dy:Number):void {
      x += dx;
      y += dy;
    }

    public function offsetPoint(point:Point):void {
      offset(point.x, point.y);
    }

    public function contains(x:Number, y:Number):Boolean {
      return this.x <= x && x <= this.right && this.y <= y && y <= this.bottom;
    }

    public function containsPoint(point:Point):Boolean {
      return contains(point.x, point.y);
    }

    public function containsRect(rect:Rectangle):Boolean {
      return containsPoint(rect.topLeft) && containsPoint(rect.bottomRight);
    }

    public function intersection(toIntersect:Rectangle):Rectangle {
      var l:Number = Math.max(x, toIntersect.x);
      var r:Number = Math.min(right, toIntersect.right);
      if (l <= r) {
        var t:Number = Math.max(y, toIntersect.y);
        var b:Number = Math.min(bottom, toIntersect.bottom);
        if (t <= b) {
          return new Rectangle(l, t, r - l, b - t);
        }
      }
      return new Rectangle();
    }

    public function intersects(toIntersect:Rectangle):Boolean {
      return Math.max(x, toIntersect.x) <= Math.min(right, toIntersect.right)
          && Math.max(y, toIntersect.y) <= Math.min(bottom, toIntersect.bottom);
    }

    public function union(toUnion:Rectangle):Rectangle {
      if (toUnion.width == 0 || toUnion.height == 0) {
        return clone();
      }
      if (width == 0 || height == 0) {
        return toUnion.clone();
      }
      var l:Number = Math.min(x, toUnion.x);
      var t:Number = Math.min(y, toUnion.y);
      return new Rectangle(l,
                           t,
                           Math.max(right, toUnion.right) - l,
                           Math.max(bottom, toUnion.bottom) - t);
    }

    public function equals(toCompare:Rectangle):Boolean {
      return x == toCompare.x
          && y == toCompare.y
          && width == toCompare.width
          && height == toCompare.height;
    }

    public function copyFrom(sourceRect:Rectangle):void {
      x = sourceRect.x;
      y = sourceRect.y;
      width = sourceRect.width;
      height = sourceRect.height;
    }

    public function setTo(xa:Number, ya:Number, widtha:Number, heighta:Number):void {
      x = xa;
      y = ya;
      width = widtha;
      height = heighta;
    }

    public function toString():String {
      return "(x=" + x + ", y=" + y + ", w=" + width + ", h=" + height + ")";
    }
  }
}

add copyright notice

add copyright notice