// Urho2D sprite example.
// This sample demonstrates:
//     - Creating a 2D scene with spriter animation
//     - Displaying the scene using the Renderer subsystem
//     - Handling keyboard to move and zoom 2D camera

#include "Scripts/Utilities/Sample.as"

Node@ spriterNode;
int spriterAnimationIndex = 0;

void Start()
{
    // Execute the common startup for samples
    SampleStart();

    // Create the scene content
    CreateScene();

    // Create the UI content
    CreateInstructions();

    // Setup the viewport for displaying the scene
    SetupViewport();

    // Hook up to the frame update events
    SubscribeToEvents();
}

void CreateScene()
{
    scene_ = Scene();

    // Create the Octree component to the scene. This is required before adding any drawable components, or else nothing will
    // show up. The default octree volume will be from (-1000, -1000, -1000) to (1000, 1000, 1000) in world coordinates; it
    // is also legal to place objects outside the volume but their visibility can then not be checked in a hierarchically
    // optimizing manner
    scene_.CreateComponent("Octree");

    // Create a scene node for the camera, which we will move around
    // The camera will use default settings (1000 far clip distance, 45 degrees FOV, set aspect ratio automatically)
    cameraNode = scene_.CreateChild("Camera");
    // Set an initial position for the camera scene node above the plane
    cameraNode.position = Vector3(0.0f, 0.0f, -10.0f);

    Camera@ camera = cameraNode.CreateComponent("Camera");
    camera.orthographic = true;
    camera.orthoSize = graphics.height * PIXEL_SIZE;
    camera.zoom = 1.5f * Min(graphics.width / 1280.0f, graphics.height / 800.0f); // Set zoom according to user's resolution to ensure full visibility (initial zoom (1.5) is set for full visibility at 1280x800 resolution)

    AnimationSet2D@ spriterAnimationSet = cache.GetResource("AnimationSet2D", "Urho2D/imp/imp.scml");
    if (spriterAnimationSet is null)
        return;

    spriterNode = scene_.CreateChild("SpriterAnimation");
    AnimatedSprite2D@ animatedSprite = spriterNode.CreateComponent("AnimatedSprite2D");
    animatedSprite.SetAnimation(spriterAnimationSet, spriterAnimationSet.GetAnimation(spriterAnimationIndex), LM_FORCE_LOOPED);
}

void CreateInstructions()
{
    // Construct new Text object, set string to display and font to use
    Text@ instructionText = ui.root.CreateChild("Text");
    instructionText.text = "Mouse click to play next animation, \nUse WASD keys to move, use PageUp PageDown keys to zoom.";
    instructionText.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15);
    instructionText.textAlignment = HA_CENTER; // Center rows in relation to each other

    // Position the text relative to the screen center
    instructionText.horizontalAlignment = HA_CENTER;
    instructionText.verticalAlignment = VA_CENTER;
    instructionText.SetPosition(0, ui.root.height / 4);
}

void SetupViewport()
{
    // Set up a viewport to the Renderer subsystem so that the 3D scene can be seen. We need to define the scene and the camera
    // at minimum. Additionally we could configure the viewport screen size and the rendering path (eg. forward / deferred) to
    // use, but now we just use full screen and default render path configured in the engine command line options
    Viewport@ viewport = Viewport(scene_, cameraNode.GetComponent("Camera"));
    renderer.viewports[0] = viewport;
}

void MoveCamera(float timeStep)
{
    // Do not move if the UI has a focused element (the console)
    if (ui.focusElement !is null)
        return;

    // Movement speed as world units per second
    const float MOVE_SPEED = 4.0f;

    // Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
    if (input.keyDown['W'])
        cameraNode.Translate(Vector3(0.0f, 1.0f, 0.0f) * MOVE_SPEED * timeStep);
    if (input.keyDown['S'])
        cameraNode.Translate(Vector3(0.0f, -1.0f, 0.0f) * MOVE_SPEED * timeStep);
    if (input.keyDown['A'])
        cameraNode.Translate(Vector3(-1.0f, 0.0f, 0.0f) * MOVE_SPEED * timeStep);
    if (input.keyDown['D'])
        cameraNode.Translate(Vector3(1.0f, 0.0f, 0.0f) * MOVE_SPEED * timeStep);

    if (input.keyDown[KEY_PAGEUP])
    {
        Camera@ camera = cameraNode.GetComponent("Camera");
        camera.zoom = camera.zoom * 1.01f;
    }

    if (input.keyDown[KEY_PAGEDOWN])
    {
        Camera@ camera = cameraNode.GetComponent("Camera");
        camera.zoom = camera.zoom * 0.99f;
    }
}

void SubscribeToEvents()
{
    // Subscribe HandleUpdate() function for processing update events
    SubscribeToEvent("Update", "HandleUpdate");
    SubscribeToEvent("MouseButtonDown", "HandleMouseButtonDown");

    // Unsubscribe the SceneUpdate event from base class to prevent camera pitch and yaw in 2D sample
    UnsubscribeFromEvent("SceneUpdate");
}

void HandleUpdate(StringHash eventType, VariantMap& eventData)
{
    // Take the frame time step, which is stored as a float
    float timeStep = eventData["TimeStep"].GetFloat();

    // Move the camera, scale movement with time step
    MoveCamera(timeStep);
}

void HandleMouseButtonDown(StringHash eventType, VariantMap& eventData)
{
    AnimatedSprite2D@ spriterAnimatedSprite = spriterNode.GetComponent("AnimatedSprite2D");
    AnimationSet2D@ spriterAnimationSet = spriterAnimatedSprite.animationSet;
    spriterAnimationIndex = (spriterAnimationIndex + 1) % spriterAnimationSet.numAnimations;
    spriterAnimatedSprite.SetAnimation(spriterAnimationSet.GetAnimation(spriterAnimationIndex), LM_FORCE_LOOPED);
}

// Create XML patch instructions for screen joystick layout specific to this sample app
String patchInstructions =
        "<patch>" +
        "    <remove sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]/attribute[@name='Is Visible']\" />" +
        "    <replace sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]/element[./attribute[@name='Name' and @value='Label']]/attribute[@name='Text']/@value\">Zoom In</replace>" +
        "    <add sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]\">" +
        "        <element type=\"Text\">" +
        "            <attribute name=\"Name\" value=\"KeyBinding\" />" +
        "            <attribute name=\"Text\" value=\"PAGEUP\" />" +
        "        </element>" +
        "    </add>" +
        "    <remove sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]/attribute[@name='Is Visible']\" />" +
        "    <replace sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]/element[./attribute[@name='Name' and @value='Label']]/attribute[@name='Text']/@value\">Zoom Out</replace>" +
        "    <add sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]\">" +
        "        <element type=\"Text\">" +
        "            <attribute name=\"Name\" value=\"KeyBinding\" />" +
        "            <attribute name=\"Text\" value=\"PAGEDOWN\" />" +
        "        </element>" +
        "    </add>" +
        "</patch>";

// Urho2D sprite example.
// This sample demonstrates:
//     - Creating a 2D scene with spriter animation
//     - Displaying the scene using the Renderer subsystem
//     - Handling keyboard to move and zoom 2D camera

#include "Scripts/Utilities/Sample.as"

Node@ spriterNode;
int spriterAnimationIndex = 0;

void Start()
{
    // Execute the common startup for samples
    SampleStart();

    // Create the scene content
    CreateScene();

    // Create the UI content
    CreateInstructions();

    // Setup the viewport for displaying the scene
    SetupViewport();

    // Hook up to the frame update events
    SubscribeToEvents();
}

void CreateScene()
{
    scene_ = Scene();

    // Create the Octree component to the scene. This is required before adding any drawable components, or else nothing will
    // show up. The default octree volume will be from (-1000, -1000, -1000) to (1000, 1000, 1000) in world coordinates; it
    // is also legal to place objects outside the volume but their visibility can then not be checked in a hierarchically
    // optimizing manner
    scene_.CreateComponent("Octree");

    // Create a scene node for the camera, which we will move around
    // The camera will use default settings (1000 far clip distance, 45 degrees FOV, set aspect ratio automatically)
    cameraNode = scene_.CreateChild("Camera");
    // Set an initial position for the camera scene node above the plane
    cameraNode.position = Vector3(0.0f, 0.0f, -10.0f);

    Camera@ camera = cameraNode.CreateComponent("Camera");
    camera.orthographic = true;
    camera.orthoSize = graphics.height * PIXEL_SIZE;
    camera.zoom = 1.5f * Min(graphics.width / 1280.0f, graphics.height / 800.0f); // Set zoom according to user's resolution to ensure full visibility (initial zoom (1.5) is set for full visibility at 1280x800 resolution)

    AnimationSet2D@ spriterAnimationSet = cache.GetResource("AnimationSet2D", "Urho2D/imp/imp.scml");
    if (spriterAnimationSet is null)
        return;

    spriterNode = scene_.CreateChild("SpriterAnimation");
    AnimatedSprite2D@ spriterAnimatedSprite = spriterNode.CreateComponent("AnimatedSprite2D");
    spriterAnimatedSprite.SetAnimation(spriterAnimationSet, spriterAnimationSet.GetAnimation(spriterAnimationIndex), LM_FORCE_LOOPED);
}

void CreateInstructions()
{
    // Construct new Text object, set string to display and font to use
    Text@ instructionText = ui.root.CreateChild("Text");
    instructionText.text = "Mouse click to play next animation, \nUse WASD keys to move, use PageUp PageDown keys to zoom.";
    instructionText.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15);
    instructionText.textAlignment = HA_CENTER; // Center rows in relation to each other

    // Position the text relative to the screen center
    instructionText.horizontalAlignment = HA_CENTER;
    instructionText.verticalAlignment = VA_CENTER;
    instructionText.SetPosition(0, ui.root.height / 4);
}

void SetupViewport()
{
    // Set up a viewport to the Renderer subsystem so that the 3D scene can be seen. We need to define the scene and the camera
    // at minimum. Additionally we could configure the viewport screen size and the rendering path (eg. forward / deferred) to
    // use, but now we just use full screen and default render path configured in the engine command line options
    Viewport@ viewport = Viewport(scene_, cameraNode.GetComponent("Camera"));
    renderer.viewports[0] = viewport;
}

void MoveCamera(float timeStep)
{
    // Do not move if the UI has a focused element (the console)
    if (ui.focusElement !is null)
        return;

    // Movement speed as world units per second
    const float MOVE_SPEED = 4.0f;

    // Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
    if (input.keyDown['W'])
        cameraNode.Translate(Vector3(0.0f, 1.0f, 0.0f) * MOVE_SPEED * timeStep);
    if (input.keyDown['S'])
        cameraNode.Translate(Vector3(0.0f, -1.0f, 0.0f) * MOVE_SPEED * timeStep);
    if (input.keyDown['A'])
        cameraNode.Translate(Vector3(-1.0f, 0.0f, 0.0f) * MOVE_SPEED * timeStep);
    if (input.keyDown['D'])
        cameraNode.Translate(Vector3(1.0f, 0.0f, 0.0f) * MOVE_SPEED * timeStep);

    if (input.keyDown[KEY_PAGEUP])
    {
        Camera@ camera = cameraNode.GetComponent("Camera");
        camera.zoom = camera.zoom * 1.01f;
    }

    if (input.keyDown[KEY_PAGEDOWN])
    {
        Camera@ camera = cameraNode.GetComponent("Camera");
        camera.zoom = camera.zoom * 0.99f;
    }
}

void SubscribeToEvents()
{
    // Subscribe HandleUpdate() function for processing update events
    SubscribeToEvent("Update", "HandleUpdate");
    SubscribeToEvent("MouseButtonDown", "HandleMouseButtonDown");

    // Unsubscribe the SceneUpdate event from base class to prevent camera pitch and yaw in 2D sample
    UnsubscribeFromEvent("SceneUpdate");
}

void HandleUpdate(StringHash eventType, VariantMap& eventData)
{
    // Take the frame time step, which is stored as a float
    float timeStep = eventData["TimeStep"].GetFloat();

    // Move the camera, scale movement with time step
    MoveCamera(timeStep);
}

void HandleMouseButtonDown(StringHash eventType, VariantMap& eventData)
{
    AnimatedSprite2D@ spriterAnimatedSprite = spriterNode.GetComponent("AnimatedSprite2D");
    AnimationSet2D@ spriterAnimationSet = spriterAnimatedSprite.animationSet;
    spriterAnimationIndex = (spriterAnimationIndex + 1) % spriterAnimationSet.numAnimations;
    spriterAnimatedSprite.SetAnimation(spriterAnimationSet.GetAnimation(spriterAnimationIndex), LM_FORCE_LOOPED);
}

// Create XML patch instructions for screen joystick layout specific to this sample app
String patchInstructions =
        "<patch>" +
        "    <remove sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]/attribute[@name='Is Visible']\" />" +
        "    <replace sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]/element[./attribute[@name='Name' and @value='Label']]/attribute[@name='Text']/@value\">Zoom In</replace>" +
        "    <add sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]\">" +
        "        <element type=\"Text\">" +
        "            <attribute name=\"Name\" value=\"KeyBinding\" />" +
        "            <attribute name=\"Text\" value=\"PAGEUP\" />" +
        "        </element>" +
        "    </add>" +
        "    <remove sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]/attribute[@name='Is Visible']\" />" +
        "    <replace sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]/element[./attribute[@name='Name' and @value='Label']]/attribute[@name='Text']/@value\">Zoom Out</replace>" +
        "    <add sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]\">" +
        "        <element type=\"Text\">" +
        "            <attribute name=\"Name\" value=\"KeyBinding\" />" +
        "            <attribute name=\"Text\" value=\"PAGEDOWN\" />" +
        "        </element>" +
        "    </add>" +
        "</patch>";

rename variable.

rename variable.


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

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

use namespace dolly_internal;

public class ClonerTest {

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

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

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

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

		classLevelCloneableType = Type.forInstance(classLevelCloneable);
		propertyLevelCloneableType = Type.forInstance(propertyLevelCloneable);
	}

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

	[Test]
	public function testGetCloneableFieldsForType():void {
		var cloneableFields:Vector.<Field> = Cloner.getCloneableFieldsForType(classLevelCloneableType);

		assertNotNull(cloneableFields);
		assertEquals(4, cloneableFields.length);

		cloneableFields = Cloner.getCloneableFieldsForType(propertyLevelCloneableType);

		assertNotNull(cloneableFields);
		assertEquals(3, cloneableFields.length);
	}

	[Test]
	public function testCloneWithClassLevelMetadata():void {
		const clone:ClassLevelCloneable = Cloner.clone(classLevelCloneable) as ClassLevelCloneable;

		assertNotNull(clone);
		assertNotNull(clone.property1);
		assertEquals(clone.property1, classLevelCloneable.property1);
		assertNotNull(clone.property2);
		assertEquals(clone.property2, classLevelCloneable.property2);
		assertNotNull(clone.property3);
		assertEquals(clone.property3, classLevelCloneable.property3);
		assertNotNull(clone.writableField);
		assertEquals(clone.writableField, classLevelCloneable.writableField);
	}

	[Test]
	public function testCloneWithPropertyLevelMetadata():void {
		const clone:PropertyLevelCloneable = Cloner.clone(
				propertyLevelCloneable
		) as PropertyLevelCloneable;

		assertNotNull(clone);
		assertNull(clone.property1);
		assertNotNull(clone.property2);
		assertEquals(clone.property2, propertyLevelCloneable.property2);
		assertNotNull(clone.property3);
		assertEquals(clone.property3, propertyLevelCloneable.property3);
		assertNotNull(clone.writableField);
		assertEquals(clone.writableField, propertyLevelCloneable.writableField);
	}
}
}

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

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

use namespace dolly_internal;

public class ClonerTest {

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

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

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

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

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

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

		propertyLevelCloneableType = Type.forInstance(propertyLevelCloneable);
	}

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

		classLevelCopyableCloneable = null;
		classLevelCopyableCloneableType = null;

		propertyLevelCloneable = null;
		propertyLevelCloneableType = null;
	}

	[Test]
	public function testGetCloneableFieldsForType():void {
		var cloneableFields:Vector.<Field> = Cloner.getCloneableFieldsForType(classLevelCloneableType);

		assertNotNull(cloneableFields);
		assertEquals(4, cloneableFields.length);

		cloneableFields = Cloner.getCloneableFieldsForType(classLevelCloneableType);
		assertNotNull(cloneableFields);
		assertEquals(4, cloneableFields.length);

		cloneableFields = Cloner.getCloneableFieldsForType(propertyLevelCloneableType);

		assertNotNull(cloneableFields);
		assertEquals(3, cloneableFields.length);
	}

	[Test]
	public function testCloneWithClassLevelMetadata():void {
		const clone:ClassLevelCloneable = Cloner.clone(classLevelCloneable) as ClassLevelCloneable;

		assertNotNull(clone);
		assertNotNull(clone.property1);
		assertEquals(clone.property1, classLevelCloneable.property1);
		assertNotNull(clone.property2);
		assertEquals(clone.property2, classLevelCloneable.property2);
		assertNotNull(clone.property3);
		assertEquals(clone.property3, classLevelCloneable.property3);
		assertNotNull(clone.writableField);
		assertEquals(clone.writableField, classLevelCloneable.writableField);
	}

	[Test]
	public function testCloneWithPropertyLevelMetadata():void {
		const clone:PropertyLevelCloneable = Cloner.clone(
				propertyLevelCloneable
		) as PropertyLevelCloneable;

		assertNotNull(clone);
		assertNull(clone.property1);
		assertNotNull(clone.property2);
		assertEquals(clone.property2, propertyLevelCloneable.property2);
		assertNotNull(clone.property3);
		assertEquals(clone.property3, propertyLevelCloneable.property3);
		assertNotNull(clone.writableField);
		assertEquals(clone.writableField, propertyLevelCloneable.writableField);
	}
}
}

Test ClassLevelCopyableCloneable class in ClonerTest.


package widgets.supportClasses
{

import com.esri.ags.FeatureSet;
import com.esri.ags.Graphic;
import com.esri.ags.layers.FeatureLayer;
import com.esri.ags.layers.supportClasses.CodedValue;
import com.esri.ags.layers.supportClasses.CodedValueDomain;
import com.esri.ags.layers.supportClasses.FeatureType;
import com.esri.ags.layers.supportClasses.Field;
import com.esri.ags.layers.supportClasses.LayerDetails;

import mx.core.FlexGlobals;
import mx.core.LayoutDirection;
import mx.formatters.DateFormatter;

[Bindable]
public class ResultAttributes
{
    public var attributes:Object;
    public var title:String;
    public var content:String;
    public var link:String;
    public var linkAlias:String;

    public static function toResultAttributes(fields:XMLList,
                                              graphic:Graphic = null,
                                              featureSet:FeatureSet = null,
                                              layer:FeatureLayer = null,
                                              layerDetails:LayerDetails = null,
                                              fallbackTitle:String = null,
                                              titleField:String = null,
                                              linkField:String = null,
                                              linkAlias:String = null):ResultAttributes
    {
        var resultAttributes:ResultAttributes = new ResultAttributes;

        var value:String = "";
        var title:String = "";
        var content:String = "";
        var link:String = "";
        var linkAlias:String;

        var fieldsXMLList:XMLList = fields ? fields.field : null;
        if (fields && fields[0].@all[0] == "true")
        {
            if (layerDetails.fields)
            {
                for each (var field:Field in layerDetails.fields)
                {
                    if (field.name in graphic.attributes)
                    {
                        displayFields(field.name, getFieldXML(field.name, fieldsXMLList), field);
                    }
                }
            }
            else
            {
                for (var fieldName:String in graphic.attributes)
                {
                    displayFields(fieldName, getFieldXML(fieldName, fieldsXMLList), null);
                }
            }
        }
        else
        {
            for each (var fieldXML:XML in fieldsXMLList) // display the fields in the same order as specified
            {
                if (fieldXML.@name[0] in graphic.attributes)
                {
                    displayFields(fieldXML.@name[0], fieldXML, getField(layer, fieldXML.@name[0]));
                }
            }
        }

        resultAttributes.attributes = graphic.attributes;
        resultAttributes.title = title ? title : fallbackTitle;
        resultAttributes.content = content;
        resultAttributes.link = link ? link : null;
        resultAttributes.linkAlias = linkAlias;

        function displayFields(fieldName:String, fieldXML:XML, field:Field):void
        {
            var fieldNameTextValue:String = graphic.attributes[fieldName];
            value = fieldNameTextValue ? fieldNameTextValue : "";

            if (value)
            {
                var isDateField:Boolean;
                var useUTC:Boolean;
                var dateFormat:String;
                if (fieldXML)
                {
                    useUTC = fieldXML.format.@useutc[0] || fieldXML.@useutc[0] == "true";
                    dateFormat = fieldXML.format.@dateformat[0] || fieldXML.@dateformat[0];
                    if (dateFormat)
                    {
                        isDateField = true;
                    }
                }
                if (!isDateField && field)
                {
                    isDateField = field.type == Field.TYPE_DATE;
                }
                if (isDateField)
                {
                    var dateMS:Number = Number(value);
                    if (!isNaN(dateMS))
                    {
                        value = msToDate(dateMS, dateFormat, useUTC);
                    }
                }
                else
                {
                    var typeID:String = layerDetails.typeIdField ? graphic.attributes[layerDetails.typeIdField] : null;
                    if (fieldName == layerDetails.typeIdField)
                    {
                        var featureType:FeatureType = getFeatureType(layer, typeID);
                        if (featureType && featureType.name)
                        {
                            value = featureType.name;
                        }
                    }
                    else
                    {
                        var codedValue:CodedValue = getCodedValue(layer, fieldName, value, typeID);
                        if (codedValue)
                        {
                            value = codedValue.name;
                        }
                    }
                }
            }

            if (titleField && fieldName.toUpperCase() == titleField.toUpperCase())
            {
                title = value;
            }
            else if (linkField && fieldName.toUpperCase() == linkField.toUpperCase())
            {
                link = value;
                linkAlias = linkAlias;
            }
            else if (fieldName.toUpperCase() != "SHAPE_LENGTH" && fieldName.toUpperCase() != "SHAPE_AREA")
            {
                var fieldLabel:String;

                if (fieldXML && fieldXML.@alias[0])
                {
                    fieldLabel = fieldXML.@alias[0];
                }
                else
                {
                    fieldLabel = featureSet.fieldAliases[fieldName];
                }

                if (FlexGlobals.topLevelApplication.layoutDirection == LayoutDirection.RTL)
                {
                    content += value + " :" + fieldLabel + "\n";
                }
                else
                {
                    content += fieldLabel + ": " + value + "\n";
                }
            }
        }

        return resultAttributes;
    }

    private static function getFieldXML(fieldName:String, fields:XMLList):XML
    {
        var result:XML;

        for each (var fieldXML:XML in fields)
        {
            if (fieldName == fieldXML.@name[0])
            {
                result = fieldXML;
                break;
            }
        }

        return result;
    }

    private static function getField(layer:FeatureLayer, fieldName:String):Field
    {
        var result:Field;

        if (layer)
        {
            for each (var field:Field in layer.layerDetails.fields)
            {
                if (fieldName == field.name)
                {
                    result = field;
                    break;
                }
            }
        }

        return result;
    }

    private static function getFeatureType(layer:FeatureLayer, typeID:String):FeatureType
    {
        var result:FeatureType;

        if (layer)
        {
            for each (var featureType:FeatureType in layer.layerDetails.types)
            {
                if (typeID == featureType.id)
                {
                    result = featureType;
                    break;
                }
            }
        }

        return result;
    }

    private static function msToDate(ms:Number, dateFormat:String, useUTC:Boolean):String
    {
        var date:Date = new Date(ms);
        if (date.milliseconds == 999) // workaround for REST bug
        {
            date.milliseconds++;
        }
        if (useUTC)
        {
            date.minutes += date.timezoneOffset;
        }

        if (dateFormat)
        {
            var dateFormatter:DateFormatter = new DateFormatter();
            dateFormatter.formatString = dateFormat;
            var result:String = dateFormatter.format(date);
            if (result)
            {
                return result;
            }
            else
            {
                return dateFormatter.error;
            }
        }
        else
        {
            return date.toLocaleString();
        }
    }

    private static function getCodedValue(layer:FeatureLayer, fieldName:String, fieldValue:String, typeID:String):CodedValue
    {
        var result:CodedValue;

        var codedValueDomain:CodedValueDomain;
        if (typeID)
        {
            var featureType:FeatureType = getFeatureType(layer, typeID);
            if (featureType)
            {
                codedValueDomain = featureType.domains[fieldName] as CodedValueDomain;
            }
        }
        else
        {
            var field:Field = getField(layer, fieldName);
            if (field)
            {
                codedValueDomain = field.domain as CodedValueDomain;
            }
        }

        if (codedValueDomain)
        {
            for each (var codedValue:CodedValue in codedValueDomain.codedValues)
            {
                if (fieldValue == codedValue.code)
                {
                    result = codedValue;
                    break;
                }
            }
        }

        return result;
    }
}

}

package widgets.supportClasses
{

import com.esri.ags.FeatureSet;
import com.esri.ags.Graphic;
import com.esri.ags.layers.FeatureLayer;
import com.esri.ags.layers.supportClasses.CodedValue;
import com.esri.ags.layers.supportClasses.CodedValueDomain;
import com.esri.ags.layers.supportClasses.FeatureType;
import com.esri.ags.layers.supportClasses.Field;
import com.esri.ags.layers.supportClasses.LayerDetails;

import mx.core.FlexGlobals;
import mx.core.LayoutDirection;
import mx.formatters.DateFormatter;

[Bindable]
public class ResultAttributes
{
    public var attributes:Object;
    public var title:String;
    public var content:String;
    public var link:String;
    public var linkAlias:String;

    public static function toResultAttributes(fields:XMLList,
                                              graphic:Graphic = null,
                                              featureSet:FeatureSet = null,
                                              layer:FeatureLayer = null,
                                              layerDetails:LayerDetails = null,
                                              fallbackTitle:String = null,
                                              titleField:String = null,
                                              linkField:String = null,
                                              linkAlias:String = null):ResultAttributes
    {
        var resultAttributes:ResultAttributes = new ResultAttributes;

        var value:String = "";
        var title:String = "";
        var content:String = "";
        var link:String = "";
        var linkAlias:String;

        var fieldsXMLList:XMLList = fields ? fields.field : null;
        if (fields && fields[0].@all[0] == "true")
        {
            if (layerDetails.fields)
            {
                for each (var field:Field in layerDetails.fields)
                {
                    if (field.name in graphic.attributes)
                    {
                        displayFields(field.name, getFieldXML(field.name, fieldsXMLList), field);
                    }
                }
            }
            else
            {
                for (var fieldName:String in graphic.attributes)
                {
                    displayFields(fieldName, getFieldXML(fieldName, fieldsXMLList), null);
                }
            }
        }
        else
        {
            for each (var fieldXML:XML in fieldsXMLList) // display the fields in the same order as specified
            {
                if (fieldXML.@name[0] in graphic.attributes)
                {
                    displayFields(fieldXML.@name[0], fieldXML, getField(layer, fieldXML.@name[0]));
                }
            }
        }

        resultAttributes.attributes = graphic.attributes;
        resultAttributes.title = title ? title : fallbackTitle;
        resultAttributes.content = content.replace(/\n$/, '');
        resultAttributes.link = link ? link : null;
        resultAttributes.linkAlias = linkAlias;

        function displayFields(fieldName:String, fieldXML:XML, field:Field):void
        {
            var fieldNameTextValue:String = graphic.attributes[fieldName];
            value = fieldNameTextValue ? fieldNameTextValue : "";

            if (value)
            {
                var isDateField:Boolean;
                var useUTC:Boolean;
                var dateFormat:String;
                if (fieldXML)
                {
                    useUTC = fieldXML.format.@useutc[0] || fieldXML.@useutc[0] == "true";
                    dateFormat = fieldXML.format.@dateformat[0] || fieldXML.@dateformat[0];
                    if (dateFormat)
                    {
                        isDateField = true;
                    }
                }
                if (!isDateField && field)
                {
                    isDateField = field.type == Field.TYPE_DATE;
                }
                if (isDateField)
                {
                    var dateMS:Number = Number(value);
                    if (!isNaN(dateMS))
                    {
                        value = msToDate(dateMS, dateFormat, useUTC);
                    }
                }
                else
                {
                    var typeID:String = layerDetails.typeIdField ? graphic.attributes[layerDetails.typeIdField] : null;
                    if (fieldName == layerDetails.typeIdField)
                    {
                        var featureType:FeatureType = getFeatureType(layer, typeID);
                        if (featureType && featureType.name)
                        {
                            value = featureType.name;
                        }
                    }
                    else
                    {
                        var codedValue:CodedValue = getCodedValue(layer, fieldName, value, typeID);
                        if (codedValue)
                        {
                            value = codedValue.name;
                        }
                    }
                }
            }

            if (titleField && fieldName.toUpperCase() == titleField.toUpperCase())
            {
                title = value;
            }
            else if (linkField && fieldName.toUpperCase() == linkField.toUpperCase())
            {
                link = value;
                linkAlias = linkAlias;
            }
            else if (fieldName.toUpperCase() != "SHAPE_LENGTH" && fieldName.toUpperCase() != "SHAPE_AREA")
            {
                var fieldLabel:String;

                if (fieldXML && fieldXML.@alias[0])
                {
                    fieldLabel = fieldXML.@alias[0];
                }
                else
                {
                    fieldLabel = featureSet.fieldAliases[fieldName];
                }

                if (FlexGlobals.topLevelApplication.layoutDirection == LayoutDirection.RTL)
                {
                    content += value + " :" + fieldLabel + "\n";
                }
                else
                {
                    content += fieldLabel + ": " + value + "\n";
                }
            }
        }

        return resultAttributes;
    }

    private static function getFieldXML(fieldName:String, fields:XMLList):XML
    {
        var result:XML;

        for each (var fieldXML:XML in fields)
        {
            if (fieldName == fieldXML.@name[0])
            {
                result = fieldXML;
                break;
            }
        }

        return result;
    }

    private static function getField(layer:FeatureLayer, fieldName:String):Field
    {
        var result:Field;

        if (layer)
        {
            for each (var field:Field in layer.layerDetails.fields)
            {
                if (fieldName == field.name)
                {
                    result = field;
                    break;
                }
            }
        }

        return result;
    }

    private static function getFeatureType(layer:FeatureLayer, typeID:String):FeatureType
    {
        var result:FeatureType;

        if (layer)
        {
            for each (var featureType:FeatureType in layer.layerDetails.types)
            {
                if (typeID == featureType.id)
                {
                    result = featureType;
                    break;
                }
            }
        }

        return result;
    }

    private static function msToDate(ms:Number, dateFormat:String, useUTC:Boolean):String
    {
        var date:Date = new Date(ms);
        if (date.milliseconds == 999) // workaround for REST bug
        {
            date.milliseconds++;
        }
        if (useUTC)
        {
            date.minutes += date.timezoneOffset;
        }

        if (dateFormat)
        {
            var dateFormatter:DateFormatter = new DateFormatter();
            dateFormatter.formatString = dateFormat;
            var result:String = dateFormatter.format(date);
            if (result)
            {
                return result;
            }
            else
            {
                return dateFormatter.error;
            }
        }
        else
        {
            return date.toLocaleString();
        }
    }

    private static function getCodedValue(layer:FeatureLayer, fieldName:String, fieldValue:String, typeID:String):CodedValue
    {
        var result:CodedValue;

        var codedValueDomain:CodedValueDomain;
        if (typeID)
        {
            var featureType:FeatureType = getFeatureType(layer, typeID);
            if (featureType)
            {
                codedValueDomain = featureType.domains[fieldName] as CodedValueDomain;
            }
        }
        else
        {
            var field:Field = getField(layer, fieldName);
            if (field)
            {
                codedValueDomain = field.domain as CodedValueDomain;
            }
        }

        if (codedValueDomain)
        {
            for each (var codedValue:CodedValue in codedValueDomain.codedValues)
            {
                if (fieldValue == codedValue.code)
                {
                    result = codedValue;
                    break;
                }
            }
        }

        return result;
    }
}

}

Remove trailing ResultAttributes#toResultAttributes() content newlines.


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

package com.threerings.io {

import flash.utils.ByteArray;
import flash.utils.Dictionary;

import com.threerings.util.ByteEnum;
import com.threerings.util.ClassUtil;
import com.threerings.util.Enum;

import com.threerings.io.streamers.ArrayStreamer;
import com.threerings.io.streamers.ByteArrayStreamer;
import com.threerings.io.streamers.ByteEnumStreamer;
import com.threerings.io.streamers.EnumStreamer;
import com.threerings.io.streamers.NumberStreamer;
import com.threerings.io.streamers.StringStreamer;

public class Streamer
{
    public static function getStreamer (obj :Object) :Streamer
    {
        var jname :String;
        if (obj is TypedArray) {
            jname = TypedArray(obj).getJavaType();
        } else {
            jname = Translations.getToServer(ClassUtil.getClassName(obj));
        }
        return getStreamerByJavaName(jname);
    }

    public static function getStreamerByJavaName (jname :String) :Streamer
    {
        initStreamers();
        // unstream lists as simple arrays
        if (jname == "java.util.List" || jname == "java.util.ArrayList") {
            jname = "[Ljava.lang.Object;";
        }

        // see if we have a streamer for it
        var streamer :Streamer = _byJName[jname] as Streamer;
        if (streamer != null) {
            return streamer;
        }

        // see if it's an array that we unstream using an ArrayStreamer
        if (jname.charAt(0) === "[") {
            streamer = new ArrayStreamer(jname);

        } else {
            // otherwise see if it represents a Streamable
            // usually this is called from ObjectInputStream, but when it's called from
            // ObjectOutputStream it's a bit annoying, because we started with a class/object.
            // But: the code is smaller, so that wins
            var clazz :Class = ClassUtil.getClassByName(Translations.getFromServer(jname));

            if (ClassUtil.isAssignableAs(Enum, clazz)) {
                streamer = ClassUtil.isAssignableAs(ByteEnum, clazz) ?
                    new ByteEnumStreamer(clazz, jname) : new EnumStreamer(clazz, jname);

            } else if (ClassUtil.isAssignableAs(Streamable, clazz)) {
                streamer = new Streamer(clazz, jname);

            } else {
                return null;
            }
        }

        // add the good new streamer
        registerStreamer(streamer);
        return streamer;
    }

    /** This should be a protected constructor. */
    public function Streamer (targ :Class, jname :String = null)
        //throws IOError
    {
        _target = targ;
        _jname = (jname != null) ? jname : Translations.getToServer(ClassUtil.getClassName(targ));
    }

    /**
     * Return the String to use to identify the class that we're streaming.
     */
    public function getJavaClassName () :String
    {
        return _jname;
    }

    public function writeObject (obj :Object, out :ObjectOutputStream) :void
        //throws IOError
    {
        (obj as Streamable).writeObject(out);
    }

    public function createObject (ins :ObjectInputStream) :Object
        //throws IOError
    {
        // actionscript is so fucked up
        return new _target();
    }

    public function readObject (obj :Object, ins :ObjectInputStream) :void
        //throws IOError
    {
        (obj as Streamable).readObject(ins);
    }

    protected static function registerStreamer (st :Streamer) :void
    {
        _byJName[st.getJavaClassName()] = st;
    }

    /**
     * Initialize our streamers. This cannot simply be done statically
     * because we cannot instantiate a subclass when this class is still
     * being created. Fucking actionscript.
     */
    private static function initStreamers () :void
    {
        if (_byJName != null) {
            return;
        }
        _byJName = new Dictionary();
        for each (var c :Class in
                [ StringStreamer, NumberStreamer, ArrayStreamer, ByteArrayStreamer ]) {
            registerStreamer(Streamer(new c()));
        }
    }

    protected var _target :Class;

    protected var _jname :String;

    protected static var _byJName :Dictionary;
}
}

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

package com.threerings.io {

import flash.utils.ByteArray;
import flash.utils.Dictionary;

import com.threerings.util.ByteEnum;
import com.threerings.util.ClassUtil;
import com.threerings.util.Enum;

import com.threerings.io.streamers.ArrayStreamer;
import com.threerings.io.streamers.ByteArrayStreamer;
import com.threerings.io.streamers.ByteEnumStreamer;
import com.threerings.io.streamers.EnumStreamer;
import com.threerings.io.streamers.NumberStreamer;
import com.threerings.io.streamers.StringStreamer;

public class Streamer
{
    public static function getStreamer (obj :Object) :Streamer
    {
        var jname :String;
        if (obj is TypedArray) {
            jname = TypedArray(obj).getJavaType();
        } else {
            jname = Translations.getToServer(ClassUtil.getClassName(obj));
        }
        return getStreamerByJavaName(jname);
    }

    public static function getStreamerByJavaName (jname :String) :Streamer
    {
        initStreamers();
        // unstream lists as simple arrays
        if (jname == "java.util.List" || jname == "java.util.ArrayList") {
            jname = "[Ljava.lang.Object;";
        }

        // see if we have a streamer for it
        var streamer :Streamer = _byJName[jname] as Streamer;
        if (streamer != null) {
            return streamer;
        }

        // see if it's an array that we unstream using an ArrayStreamer
        if (jname.charAt(0) === "[") {
            streamer = new ArrayStreamer(jname);

        } else {
            // otherwise see if it represents a Streamable
            // usually this is called from ObjectInputStream, but when it's called from
            // ObjectOutputStream it's a bit annoying, because we started with a class/object.
            // But: the code is smaller, so that wins
            var clazz :Class = ClassUtil.getClassByName(Translations.getFromServer(jname));

            if (ClassUtil.isAssignableAs(Enum, clazz)) {
                streamer = ClassUtil.isAssignableAs(ByteEnum, clazz) ?
                    new ByteEnumStreamer(clazz, jname) : new EnumStreamer(clazz, jname);

            } else if (ClassUtil.isAssignableAs(Streamable, clazz)) {
                streamer = new Streamer(clazz, jname);

            } else {
                return null;
            }
        }

        // add the good new streamer
        registerStreamer(streamer);
        return streamer;
    }

    /** This should be a protected constructor. */
    public function Streamer (targ :Class, jname :String = null)
        //throws IOError
    {
        _target = targ;
        _jname = (jname != null) ? jname : Translations.getToServer(ClassUtil.getClassName(targ));
    }

    /**
     * Return the String to use to identify the class that we're streaming.
     */
    public function getJavaClassName () :String
    {
        return _jname;
    }

    public function writeObject (obj :Object, out :ObjectOutputStream) :void
        //throws IOError
    {
        (obj as Streamable).writeObject(out);
    }

    public function createObject (ins :ObjectInputStream) :Object
        //throws IOError
    {
        // actionscript is so fucked up
        return new _target();
    }

    public function readObject (obj :Object, ins :ObjectInputStream) :void
        //throws IOError
    {
        (obj as Streamable).readObject(ins);
    }

    public function toString () :String
    {
        return "[Streamer(" + _jname + ")]";
    }

    protected static function registerStreamer (st :Streamer) :void
    {
        _byJName[st.getJavaClassName()] = st;
    }

    /**
     * Initialize our streamers. This cannot simply be done statically
     * because we cannot instantiate a subclass when this class is still
     * being created. Fucking actionscript.
     */
    private static function initStreamers () :void
    {
        if (_byJName != null) {
            return;
        }
        _byJName = new Dictionary();
        for each (var c :Class in
                [ StringStreamer, NumberStreamer, ArrayStreamer, ByteArrayStreamer ]) {
            registerStreamer(Streamer(new c()));
        }
    }

    protected var _target :Class;

    protected var _jname :String;

    protected static var _byJName :Dictionary;
}
}

Add a better toString, for debugging.

Add a better toString, for debugging.

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


/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package com.pivotshare.hls.loader {

    import flash.display.DisplayObject;
    import flash.events.*;
    import flash.net.*;
    import flash.utils.ByteArray;
    import flash.utils.getTimer;
    import flash.utils.Timer;
    import org.mangui.hls.constant.HLSLoaderTypes;
    import org.mangui.hls.event.HLSError;
    import org.mangui.hls.event.HLSEvent;
    import org.mangui.hls.event.HLSLoadMetrics;
    import org.mangui.hls.HLS;
    import org.mangui.hls.model.Fragment;
    import org.mangui.hls.model.FragmentData;
    import org.mangui.hls.utils.AES;

    CONFIG::LOGGING {
        import org.mangui.hls.utils.Log;
        import org.mangui.hls.utils.Hex;
    }

    /**
     * HLS Fragment Streamer, which also handles decryption.
     * Tries to parallel URLStream design pattern, but is incomplete.
     *
     * See [Reading and writing a ByteArray](http://help.adobe.com/en_US/as3/dev/WS5b3ccc516d4fbf351e63e3d118666ade46-7d54.html)
     *
     * @class    FragmentStream
     * @extends  EventDispatcher
     * @author   HGPA
     */
    public class FragmentStream extends EventDispatcher {

        /*
         * DisplayObject needed by AES decrypter.
         */
        private var _displayObject : DisplayObject;

        /*
         * Fragment being loaded.
         */
        private var _fragment : Fragment;

        /*
         * URLStream used to download current Fragment.
         */
        private var _fragmentURLStream : URLStream;

        /**
         * Create a FragmentStream.
         *
         * This constructor takes a reference to main DisplayObject, e.g. stage,
         * necessary for AES decryption control.
         *
         * TODO: It would be more appropriate to create a factory that itself
         * takes the DisplayObject (or a more flexible version of AES).
         *
         * @constructor
         * @param  {DisplayObject}  displayObject
         */
        public function FragmentStream(displayObject : DisplayObject) : void {
            _displayObject = displayObject;
        };

        /*
         * Return FragmentData of Fragment currently being downloaded.
         * Of immediate interest is the `bytes` field.
         *
         * @method getFragment
         * @return  {Fragment}
         */
        public function getFragment() : Fragment {
            return _fragment;
        }

        /*
         * Close the stream.
         *
         * @method  close
         */
        public function close() : void {
            if (_fragmentURLStream && _fragmentURLStream.connected) {
                _fragmentURLStream.close();
            }

            if (_fragment) {

                if (_fragment.data.decryptAES) {
                    _fragment.data.decryptAES.cancel();
                    _fragment.data.decryptAES = null;
                }

                // Explicitly release memory
                // http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/utils/ByteArray.html#clear()
                _fragment.data.bytes.clear();
                _fragment.data.bytes = null;
            }
        }

        /**
         * Load a Fragment.
         *
         * This class/methods DOES NOT user reference of parameter. Instead it
         * clones the Fragment and manipulates this internally.
         *
         * @method  load
         * @param   {Fragment}   fragment  -  Fragment with details (cloned)
         * @param   {ByteArray}  key       -  Encryption Key
         * @return  {HLSLoadMetrics}
         */
        public function load(fragment : Fragment, key : ByteArray) : HLSLoadMetrics {

            // Clone Fragment, with new initilizations of deep fields
            // Passing around references is what is causing problems.
            // FragmentData is initialized as part of construction
            _fragment = new Fragment(
                fragment.url,
                fragment.duration,
                fragment.level,
                fragment.seqnum,
                fragment.start_time,
                fragment.continuity,
                fragment.program_date,
                fragment.decrypt_url,
                fragment.decrypt_iv, // We need this reference
                fragment.byterange_start_offset,
                fragment.byterange_end_offset,
                new Vector.<String>()
            )

            _fragmentURLStream = new URLStream();

            // START (LOADING) METRICS
            // Event listener callbacks enclose _metrics
            var _metrics : HLSLoadMetrics = new HLSLoadMetrics(HLSLoaderTypes.FRAGMENT_MAIN);
            _metrics.level = _fragment.level;
            _metrics.id    = _fragment.seqnum;
            _metrics.loading_request_time = getTimer();

            // String used to identify fragment in debug messages
            CONFIG::LOGGING {
                var fragmentString : String = "Fragment[" + _fragment.level + "][" + _fragment.seqnum + "]";
            }

            //
            // See `onLoadProgress` first.
            //

            /*
             * Called when Fragment is processing which may inclue decryption.
             *
             * To access data, call `Fragment.getFragment`, which will include
             * all bytes loaded to this point, with FragmentData's ByteArray
             * having the expected position
             *
             * NOTE: This was `FragmentLoader._fragDecryptProgressHandler` before refactor.
             *
             * @method  onProcess
             * @param   {ByteArray}  data  -  *Portion* of data that finished processing
             */
            var onProcess : Function = function onProcess(data : ByteArray) : void {

                // Ensure byte array pointer starts at beginning
                data.position = 0;

                var bytes : ByteArray = _fragment.data.bytes;

                // Byte Range Business
                // TODO: Confirm this is still working
                if (_fragment.byterange_start_offset != -1) {

                    _fragment.data.bytes.position = _fragment.data.bytes.length;
                    _fragment.data.bytes.writeBytes(data);

                    // if we have retrieved all the data, disconnect loader and notify fragment complete
                    if (_fragment.data.bytes.length >= _fragment.byterange_end_offset) {
                        if (_fragmentURLStream.connected) {
                            _fragmentURLStream.close();
                            onProcessComplete(null);
                        }
                    }
                } else {
                    // Append data to Fragment, but then reset position to mimic
                    // expected pattern of URLStream
                    var fragmentPosition : int = _fragment.data.bytes.position;
                    _fragment.data.bytes.position = _fragment.data.bytes.length;
                    _fragment.data.bytes.writeBytes(data);
                    _fragment.data.bytes.position = fragmentPosition;
                }

                var progressEvent : ProgressEvent = new ProgressEvent(
                    ProgressEvent.PROGRESS,
                    false,
                    false,
                    _fragment.data.bytes.length, // bytesLoaded
                    _fragment.data.bytesTotal    // bytesTotal
                    );

                dispatchEvent(progressEvent);
            }

            /*
             * Called when Fragment has completed processing.
             * May or may not include decryption.
             *
             * NOTE: This was `FragmentLoader._fragDecryptCompleteHandler`
             * before refactor.
             *
             * @method  onProcessComplete
             * @param   {ByteArray}  data  -  Portion of data finished processing
             */
            var onProcessComplete : Function = function onProcessComplete() : void {

                // END DECRYPTION METRICS
                // garbage collect AES decrypter
                if (_fragment.data.decryptAES) {
                    _metrics.decryption_end_time = getTimer();
                    var decrypt_duration : Number = _metrics.decryption_end_time - _metrics.decryption_begin_time;
                    CONFIG::LOGGING {
                        Log.debug("FragmentStream#onProcessComplete: Decrypted duration/length/speed:" +
                            decrypt_duration + "/" + _fragment.data.bytesLoaded + "/" +
                            Math.round((8000 * _fragment.data.bytesLoaded / decrypt_duration) / 1024) + " kb/s");
                    }

                    _fragment.data.decryptAES = null;
                }

                var completeEvent : Event = new ProgressEvent(Event.COMPLETE);
                dispatchEvent(completeEvent);
            }

            /*
             * Called when URLStream has download a portion of the file fragment.
             * This event callback delegates to decrypter if necessary.
             * Eventually onProcess is called when the downloaded portion has
             * finished processing.
             *
             * NOTE: Was `_fragLoadCompleteHandler` before refactor
             *
             * @param  {ProgressEvent}  evt  -  Portion of data finished processing
             */
            var onLoadProgress : Function = function onProgress(evt : ProgressEvent) : void {

                // First call of onProgress for this Fragment
                // Initilize fields in Fragment and FragmentData
                if (_fragment.data.bytes == null) {

                    _fragment.data.bytes = new ByteArray();
                    _fragment.data.bytesLoaded = 0;
                    _fragment.data.bytesTotal = evt.bytesTotal;
                    _fragment.data.flushTags();

                    // NOTE: This may be wrong, as it is only called after data
                    // has been loaded.
                    _metrics.loading_begin_time = getTimer();

                    CONFIG::LOGGING {
                        Log.debug("FragmentStream#onLoadProgress: Downloaded " +
                            fragmentString + "'s first " + evt.bytesLoaded +
                            " bytes of " + evt.bytesTotal + " Total");
                    }

                    // decrypt data if needed
                    if (_fragment.decrypt_url != null) {
                        // START DECRYPTION METRICS
                        _metrics.decryption_begin_time = getTimer();
                        CONFIG::LOGGING {
                            Log.debug("FragmentStream#onLoadProgress: " + fragmentString + " needs to be decrypted");
                        }

                        _fragment.data.decryptAES = new AES(
                            _displayObject,
                            key,
                            _fragment.decrypt_iv,
                            onProcess,
                            onProcessComplete
                            );
                    } else {
                        _fragment.data.decryptAES = null;
                    }
                }

                if (evt.bytesLoaded > _fragment.data.bytesLoaded && _fragmentURLStream.bytesAvailable > 0) {  // prevent EOF error race condition

                    // bytes from URLStream
                    var data : ByteArray = new ByteArray();
                    _fragmentURLStream.readBytes(data);

                    // Mark that bytes have been loaded, but do not store these
                    // bytes yet
                    _fragment.data.bytesLoaded += data.length;

                    if (_fragment.data.decryptAES != null) {
                        _fragment.data.decryptAES.append(data);
                    } else {
                        onProcess(data);
                    }
                }
            }

            /*
             * Called when URLStream had completed downloading.
             *
             * @param  {Event}  evt  -  Portion of data finished processing
             */
            var onLoadComplete : Function = function onLoadComplete(evt : Event) : void {
                // load complete, reset retry counter
                //_fragRetryCount = 0;
                //_fragRetryTimeout = 1000;

                if (_fragment.data.bytes == null) {
                    CONFIG::LOGGING {
                        Log.warn("FragmentStream#onLoadComplete: " + fragmentString +
                            " size is null, invalidate it and load next one");
                    }
                    //_levels[_hls.loadLevel].updateFragment(_fragCurrent.seqnum, false);
                    //_loadingState = LOADING_IDLE;

                    // TODO: Dispatch an error

                    return;
                }

                CONFIG::LOGGING {
                    Log.debug("FragmentStream#onLoadComplete: " + fragmentString +
                        " has finished downloading, but may still be decrypting");
                }

                // TODO: ???
                //_fragSkipping = false;

                // END LOADING METRICS
                _metrics.loading_end_time = getTimer();
                _metrics.size = _fragment.data.bytesLoaded;
                var _loading_duration : uint = _metrics.loading_end_time - _metrics.loading_request_time;
                CONFIG::LOGGING {
                    Log.debug("FragmentStream#onLoadComplete: Loading duration/RTT/length/speed:" +
                        _loading_duration + "/" +
                        (_metrics.loading_begin_time - _metrics.loading_request_time) + "/" +
                        _metrics.size + "/" +
                        Math.round((8000 * _metrics.size / _loading_duration) / 1024) + " kb/s");
                }

                if (_fragment.data.decryptAES) {
                    _fragment.data.decryptAES.notifycomplete(); // Calls onProcessComplete by proxy
                } else {
                    onProcessComplete();
                }
            }

            /*
             * Called when URLStream has Errored.
             * @param  {ErrorEvent}  evt  -  Portion of data finished processing
             */
            var onLoadError : Function = function onLoadError(evt : ErrorEvent) : void {

                CONFIG::LOGGING {
                    Log.error("FragmentStream#onLoadError: " + evt.text);
                }

                // Forward error
                dispatchEvent(evt);
            }

            _fragmentURLStream.addEventListener(IOErrorEvent.IO_ERROR, onLoadError);
            _fragmentURLStream.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onLoadError);
            _fragmentURLStream.addEventListener(ProgressEvent.PROGRESS, onLoadProgress);
            _fragmentURLStream.addEventListener(Event.COMPLETE, onLoadComplete);

            _fragmentURLStream.load(new URLRequest(fragment.url));

            return _metrics;
        }

    }
}

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package com.pivotshare.hls.loader {

    import flash.display.DisplayObject;
    import flash.events.*;
    import flash.net.*;
    import flash.utils.ByteArray;
    import flash.utils.getTimer;
    import flash.utils.Timer;
    import org.mangui.hls.constant.HLSLoaderTypes;
    import org.mangui.hls.event.HLSError;
    import org.mangui.hls.event.HLSEvent;
    import org.mangui.hls.event.HLSLoadMetrics;
    import org.mangui.hls.HLS;
    import org.mangui.hls.model.Fragment;
    import org.mangui.hls.model.FragmentData;
    import org.mangui.hls.utils.AES;

    CONFIG::LOGGING {
        import org.mangui.hls.utils.Log;
        import org.mangui.hls.utils.Hex;
    }

    /**
     * HLS Fragment Streamer, which also handles decryption.
     * Tries to parallel URLStream design pattern, but is incomplete.
     *
     * See [Reading and writing a ByteArray](http://help.adobe.com/en_US/as3/dev/WS5b3ccc516d4fbf351e63e3d118666ade46-7d54.html)
     *
     * @class    FragmentStream
     * @extends  EventDispatcher
     * @author   HGPA
     */
    public class FragmentStream extends EventDispatcher {

        /*
         * DisplayObject needed by AES decrypter.
         */
        private var _displayObject : DisplayObject;

        /*
         * Fragment being loaded.
         */
        private var _fragment : Fragment;

        /*
         * URLStream used to download current Fragment.
         */
        private var _fragmentURLStream : URLStream;

        /**
         * Create a FragmentStream.
         *
         * This constructor takes a reference to main DisplayObject, e.g. stage,
         * necessary for AES decryption control.
         *
         * TODO: It would be more appropriate to create a factory that itself
         * takes the DisplayObject (or a more flexible version of AES).
         *
         * @constructor
         * @param  {DisplayObject}  displayObject
         */
        public function FragmentStream(displayObject : DisplayObject) : void {
            _displayObject = displayObject;
        };

        /*
         * Return FragmentData of Fragment currently being downloaded.
         * Of immediate interest is the `bytes` field.
         *
         * @method getFragment
         * @return  {Fragment}
         */
        public function getFragment() : Fragment {
            return _fragment;
        }

        /*
         * Close the stream.
         *
         * @method  close
         */
        public function close() : void {

            if (_fragmentURLStream && _fragmentURLStream.connected) {
                _fragmentURLStream.close();
            }

            if (_fragment && _fragment.data) {

                if (_fragment.data.decryptAES) {
                    _fragment.data.decryptAES.cancel();
                    _fragment.data.decryptAES = null;
                }

                // Explicitly release memory
                // http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/utils/ByteArray.html#clear()
                if (_fragment.data.bytes) {
                    _fragment.data.bytes.clear();
                    _fragment.data.bytes = null;
                }
            }
        }

        /**
         * Load a Fragment.
         *
         * This class/methods DOES NOT user reference of parameter. Instead it
         * clones the Fragment and manipulates this internally.
         *
         * @method  load
         * @param   {Fragment}   fragment  -  Fragment with details (cloned)
         * @param   {ByteArray}  key       -  Encryption Key
         * @return  {HLSLoadMetrics}
         */
        public function load(fragment : Fragment, key : ByteArray) : HLSLoadMetrics {

            // Clone Fragment, with new initilizations of deep fields
            // Passing around references is what is causing problems.
            // FragmentData is initialized as part of construction
            _fragment = new Fragment(
                fragment.url,
                fragment.duration,
                fragment.level,
                fragment.seqnum,
                fragment.start_time,
                fragment.continuity,
                fragment.program_date,
                fragment.decrypt_url,
                fragment.decrypt_iv, // We need this reference
                fragment.byterange_start_offset,
                fragment.byterange_end_offset,
                new Vector.<String>()
            )

            _fragmentURLStream = new URLStream();

            // START (LOADING) METRICS
            // Event listener callbacks enclose _metrics
            var _metrics : HLSLoadMetrics = new HLSLoadMetrics(HLSLoaderTypes.FRAGMENT_MAIN);
            _metrics.level = _fragment.level;
            _metrics.id    = _fragment.seqnum;
            _metrics.loading_request_time = getTimer();

            // String used to identify fragment in debug messages
            CONFIG::LOGGING {
                var fragmentString : String = "Fragment[" + _fragment.level + "][" + _fragment.seqnum + "]";
            }

            //
            // See `onLoadProgress` first.
            //

            /*
             * Called when Fragment is processing which may inclue decryption.
             *
             * To access data, call `Fragment.getFragment`, which will include
             * all bytes loaded to this point, with FragmentData's ByteArray
             * having the expected position
             *
             * NOTE: This was `FragmentLoader._fragDecryptProgressHandler` before refactor.
             *
             * @method  onProcess
             * @param   {ByteArray}  data  -  *Portion* of data that finished processing
             */
            var onProcess : Function = function onProcess(data : ByteArray) : void {

                // Ensure byte array pointer starts at beginning
                data.position = 0;

                var bytes : ByteArray = _fragment.data.bytes;

                // Byte Range Business
                // TODO: Confirm this is still working
                if (_fragment.byterange_start_offset != -1) {

                    _fragment.data.bytes.position = _fragment.data.bytes.length;
                    _fragment.data.bytes.writeBytes(data);

                    // if we have retrieved all the data, disconnect loader and notify fragment complete
                    if (_fragment.data.bytes.length >= _fragment.byterange_end_offset) {
                        if (_fragmentURLStream.connected) {
                            _fragmentURLStream.close();
                            onProcessComplete(null);
                        }
                    }
                } else {
                    // Append data to Fragment, but then reset position to mimic
                    // expected pattern of URLStream
                    var fragmentPosition : int = _fragment.data.bytes.position;
                    _fragment.data.bytes.position = _fragment.data.bytes.length;
                    _fragment.data.bytes.writeBytes(data);
                    _fragment.data.bytes.position = fragmentPosition;
                }

                var progressEvent : ProgressEvent = new ProgressEvent(
                    ProgressEvent.PROGRESS,
                    false,
                    false,
                    _fragment.data.bytes.length, // bytesLoaded
                    _fragment.data.bytesTotal    // bytesTotal
                    );

                dispatchEvent(progressEvent);
            }

            /*
             * Called when Fragment has completed processing.
             * May or may not include decryption.
             *
             * NOTE: This was `FragmentLoader._fragDecryptCompleteHandler`
             * before refactor.
             *
             * @method  onProcessComplete
             * @param   {ByteArray}  data  -  Portion of data finished processing
             */
            var onProcessComplete : Function = function onProcessComplete() : void {

                // END DECRYPTION METRICS
                // garbage collect AES decrypter
                if (_fragment.data.decryptAES) {
                    _metrics.decryption_end_time = getTimer();
                    var decrypt_duration : Number = _metrics.decryption_end_time - _metrics.decryption_begin_time;
                    CONFIG::LOGGING {
                        Log.debug("FragmentStream#onProcessComplete: Decrypted duration/length/speed:" +
                            decrypt_duration + "/" + _fragment.data.bytesLoaded + "/" +
                            Math.round((8000 * _fragment.data.bytesLoaded / decrypt_duration) / 1024) + " kb/s");
                    }

                    _fragment.data.decryptAES = null;
                }

                var completeEvent : Event = new ProgressEvent(Event.COMPLETE);
                dispatchEvent(completeEvent);
            }

            /*
             * Called when URLStream has download a portion of the file fragment.
             * This event callback delegates to decrypter if necessary.
             * Eventually onProcess is called when the downloaded portion has
             * finished processing.
             *
             * NOTE: Was `_fragLoadCompleteHandler` before refactor
             *
             * @param  {ProgressEvent}  evt  -  Portion of data finished processing
             */
            var onLoadProgress : Function = function onProgress(evt : ProgressEvent) : void {

                // First call of onProgress for this Fragment
                // Initilize fields in Fragment and FragmentData
                if (_fragment.data.bytes == null) {

                    _fragment.data.bytes = new ByteArray();
                    _fragment.data.bytesLoaded = 0;
                    _fragment.data.bytesTotal = evt.bytesTotal;
                    _fragment.data.flushTags();

                    // NOTE: This may be wrong, as it is only called after data
                    // has been loaded.
                    _metrics.loading_begin_time = getTimer();

                    CONFIG::LOGGING {
                        Log.debug("FragmentStream#onLoadProgress: Downloaded " +
                            fragmentString + "'s first " + evt.bytesLoaded +
                            " bytes of " + evt.bytesTotal + " Total");
                    }

                    // decrypt data if needed
                    if (_fragment.decrypt_url != null) {
                        // START DECRYPTION METRICS
                        _metrics.decryption_begin_time = getTimer();
                        CONFIG::LOGGING {
                            Log.debug("FragmentStream#onLoadProgress: " + fragmentString + " needs to be decrypted");
                        }

                        _fragment.data.decryptAES = new AES(
                            _displayObject,
                            key,
                            _fragment.decrypt_iv,
                            onProcess,
                            onProcessComplete
                            );
                    } else {
                        _fragment.data.decryptAES = null;
                    }
                }

                if (evt.bytesLoaded > _fragment.data.bytesLoaded && _fragmentURLStream.bytesAvailable > 0) {  // prevent EOF error race condition

                    // bytes from URLStream
                    var data : ByteArray = new ByteArray();
                    _fragmentURLStream.readBytes(data);

                    // Mark that bytes have been loaded, but do not store these
                    // bytes yet
                    _fragment.data.bytesLoaded += data.length;

                    if (_fragment.data.decryptAES != null) {
                        _fragment.data.decryptAES.append(data);
                    } else {
                        onProcess(data);
                    }
                }
            }

            /*
             * Called when URLStream had completed downloading.
             *
             * @param  {Event}  evt  -  Portion of data finished processing
             */
            var onLoadComplete : Function = function onLoadComplete(evt : Event) : void {
                // load complete, reset retry counter
                //_fragRetryCount = 0;
                //_fragRetryTimeout = 1000;

                if (_fragment.data.bytes == null) {
                    CONFIG::LOGGING {
                        Log.warn("FragmentStream#onLoadComplete: " + fragmentString +
                            " size is null, invalidate it and load next one");
                    }
                    //_levels[_hls.loadLevel].updateFragment(_fragCurrent.seqnum, false);
                    //_loadingState = LOADING_IDLE;

                    // TODO: Dispatch an error

                    return;
                }

                CONFIG::LOGGING {
                    Log.debug("FragmentStream#onLoadComplete: " + fragmentString +
                        " has finished downloading, but may still be decrypting");
                }

                // TODO: ???
                //_fragSkipping = false;

                // END LOADING METRICS
                _metrics.loading_end_time = getTimer();
                _metrics.size = _fragment.data.bytesLoaded;
                var _loading_duration : uint = _metrics.loading_end_time - _metrics.loading_request_time;
                CONFIG::LOGGING {
                    Log.debug("FragmentStream#onLoadComplete: Loading duration/RTT/length/speed:" +
                        _loading_duration + "/" +
                        (_metrics.loading_begin_time - _metrics.loading_request_time) + "/" +
                        _metrics.size + "/" +
                        Math.round((8000 * _metrics.size / _loading_duration) / 1024) + " kb/s");
                }

                if (_fragment.data.decryptAES) {
                    _fragment.data.decryptAES.notifycomplete(); // Calls onProcessComplete by proxy
                } else {
                    onProcessComplete();
                }
            }

            /*
             * Called when URLStream has Errored.
             * @param  {ErrorEvent}  evt  -  Portion of data finished processing
             */
            var onLoadError : Function = function onLoadError(evt : ErrorEvent) : void {

                CONFIG::LOGGING {
                    Log.error("FragmentStream#onLoadError: " + evt.text);
                }

                // Forward error
                dispatchEvent(evt);
            }

            _fragmentURLStream.addEventListener(IOErrorEvent.IO_ERROR, onLoadError);
            _fragmentURLStream.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onLoadError);
            _fragmentURLStream.addEventListener(ProgressEvent.PROGRESS, onLoadProgress);
            _fragmentURLStream.addEventListener(Event.COMPLETE, onLoadComplete);

            _fragmentURLStream.load(new URLRequest(fragment.url));

            return _metrics;
        }

    }
}

Fix destruction

[FragmentStream] Fix destruction


package aerys.minko.type.clone
{
	import aerys.minko.scene.controller.AbstractController;
	import aerys.minko.scene.controller.AnimationController;
	import aerys.minko.scene.controller.TransformController;
	import aerys.minko.scene.controller.camera.CameraController;
	import aerys.minko.scene.controller.mesh.MeshController;
	import aerys.minko.scene.controller.mesh.MeshVisibilityController;
	import aerys.minko.scene.controller.mesh.skinning.SkinningController;

	public class CloneOptions
	{
		private var _clonedControllerTypes		: Vector.<Class>	= new <Class>[];
		private var _ignoredControllerTypes		: Vector.<Class>	= new <Class>[];
		private var _reassignedControllerTypes	: Vector.<Class>	= new <Class>[];
		private var _defaultControllerAction	: uint				= ControllerCloneAction.REASSIGN;

		public function get defaultControllerAction() : uint
		{
			return _defaultControllerAction;
		}
		public function set defaultControllerAction(v : uint) : void
		{
			if (v != ControllerCloneAction.CLONE
				&& v != ControllerCloneAction.IGNORE
				&& v != ControllerCloneAction.REASSIGN)
				throw new Error('Unknown action type.');

			_defaultControllerAction = v;
		}

		public function CloneOptions()
		{
		}

		public static function get defaultOptions() : CloneOptions
		{
			var cloneOptions : CloneOptions = new CloneOptions();

			cloneOptions._clonedControllerTypes.push(
				AnimationController,
				SkinningController
			);
			cloneOptions._ignoredControllerTypes.push(
				MeshController,
				MeshVisibilityController,
				CameraController,
				TransformController
			);
			cloneOptions._defaultControllerAction = ControllerCloneAction.REASSIGN;

			return cloneOptions;
		}

		public static function get cloneAllOptions() : CloneOptions
		{
			var cloneOptions : CloneOptions = new CloneOptions();

			cloneOptions._ignoredControllerTypes.push(
				MeshController,
				MeshVisibilityController,
				CameraController,
				TransformController
			);
			cloneOptions._defaultControllerAction = ControllerCloneAction.CLONE;

			return cloneOptions;
		}

		public function addControllerAction(controllerClass : Class, action : uint) : void
		{
			switch (action)
			{
				case ControllerCloneAction.CLONE:
					_clonedControllerTypes.push(controllerClass);
					break;

				case ControllerCloneAction.IGNORE:
					_ignoredControllerTypes.push(controllerClass);
					break;

				case ControllerCloneAction.REASSIGN:
					_reassignedControllerTypes.push(controllerClass);
					break;

				default:
					throw new Error('Unknown action type.');
			}
		}

		public function removeControllerAction(controllerClass : Class) : void
		{
			throw new Error('Implement me.');
		}

		public function getActionForController(controller : AbstractController) : uint
		{
			var numControllersToClone		: uint = _clonedControllerTypes.length;
			var numControllersToIgnore		: uint = _ignoredControllerTypes.length;
			var numControllersToReassign	: uint = _reassignedControllerTypes.length;
			var controllerId				: uint;

			for (controllerId = 0; controllerId < numControllersToClone; ++controllerId)
				if (controller is _clonedControllerTypes[controllerId])
					return ControllerCloneAction.CLONE;

			for (controllerId = 0; controllerId < numControllersToIgnore; ++controllerId)
				if (controller is _ignoredControllerTypes[controllerId])
					return ControllerCloneAction.IGNORE;

			for (controllerId = 0; controllerId < numControllersToReassign; ++controllerId)
				if (controller is _reassignedControllerTypes[controllerId])
					return ControllerCloneAction.REASSIGN;

			return _defaultControllerAction;
		}

	}
}

package aerys.minko.type.clone
{
	import aerys.minko.scene.controller.AbstractController;
	import aerys.minko.scene.controller.AnimationController;
	import aerys.minko.scene.controller.TransformController;
	import aerys.minko.scene.controller.camera.CameraController;
	import aerys.minko.scene.controller.mesh.MeshController;
	import aerys.minko.scene.controller.mesh.MeshVisibilityController;
	import aerys.minko.scene.controller.mesh.skinning.SkinningController;

	public final class CloneOptions
	{
		private var _clonedControllerTypes		: Vector.<Class>;
		private var _ignoredControllerTypes		: Vector.<Class>;
		private var _reassignedControllerTypes	: Vector.<Class>;
		private var _defaultControllerAction	: uint;

		public function get defaultControllerAction() : uint
		{
			return _defaultControllerAction;
		}
		public function set defaultControllerAction(v : uint) : void
		{
			if (v != ControllerCloneAction.CLONE
				&& v != ControllerCloneAction.IGNORE
				&& v != ControllerCloneAction.REASSIGN)
				throw new Error('Unknown action type.');

			_defaultControllerAction = v;
		}

		public function CloneOptions()
		{
			initialize();
		}

		private function initialize() : void
		{
			_clonedControllerTypes = new <Class>[];
			_ignoredControllerTypes = new <Class>[];
			_reassignedControllerTypes = new <Class>[];
			_defaultControllerAction = ControllerCloneAction.REASSIGN;
		}

		public static function get defaultOptions() : CloneOptions
		{
			var cloneOptions : CloneOptions = new CloneOptions();

			cloneOptions._clonedControllerTypes.push(
				AnimationController,
				SkinningController
			);
			cloneOptions._ignoredControllerTypes.push(
				MeshController,
				MeshVisibilityController,
				CameraController,
				TransformController
			);
			cloneOptions._defaultControllerAction = ControllerCloneAction.REASSIGN;

			return cloneOptions;
		}

		public static function get cloneAllOptions() : CloneOptions
		{
			var cloneOptions : CloneOptions = new CloneOptions();

			cloneOptions._ignoredControllerTypes.push(
				MeshController,
				MeshVisibilityController,
				CameraController,
				TransformController
			);
			cloneOptions._defaultControllerAction = ControllerCloneAction.CLONE;

			return cloneOptions;
		}

		public function addControllerAction(controllerClass : Class, action : uint) : void
		{
			switch (action)
			{
				case ControllerCloneAction.CLONE:
					_clonedControllerTypes.push(controllerClass);
					break;

				case ControllerCloneAction.IGNORE:
					_ignoredControllerTypes.push(controllerClass);
					break;

				case ControllerCloneAction.REASSIGN:
					_reassignedControllerTypes.push(controllerClass);
					break;

				default:
					throw new Error('Unknown action type.');
			}
		}

		public function removeControllerAction(controllerClass : Class) : void
		{
			throw new Error('Implement me.');
		}

		public function getActionForController(controller : AbstractController) : uint
		{
			var numControllersToClone		: uint = _clonedControllerTypes.length;
			var numControllersToIgnore		: uint = _ignoredControllerTypes.length;
			var numControllersToReassign	: uint = _reassignedControllerTypes.length;
			var controllerId				: uint;

			for (controllerId = 0; controllerId < numControllersToClone; ++controllerId)
				if (controller is _clonedControllerTypes[controllerId])
					return ControllerCloneAction.CLONE;

			for (controllerId = 0; controllerId < numControllersToIgnore; ++controllerId)
				if (controller is _ignoredControllerTypes[controllerId])
					return ControllerCloneAction.IGNORE;

			for (controllerId = 0; controllerId < numControllersToReassign; ++controllerId)
				if (controller is _reassignedControllerTypes[controllerId])
					return ControllerCloneAction.REASSIGN;

			return _defaultControllerAction;
		}

	}
}

fix coding style

fix coding style


package aerys.minko.render.resource.texture
{
	import aerys.minko.render.resource.Context3DResource;

	import flash.display.BitmapData;
	import flash.display3D.Context3DTextureFormat;
	import flash.display3D.textures.Texture;
	import flash.display3D.textures.TextureBase;
	import flash.geom.Matrix;
	import flash.utils.ByteArray;

	/**
	 * @inheritdoc
	 * @author Jean-Marc Le Roux
	 *
	 */
	public final class TextureResource implements ITextureResource
	{
		private static const MAX_SIZE			: uint		= 2048;
		private static const TMP_MATRIX			: Matrix	= new Matrix();
		private static const FORMAT_BGRA		: String	= Context3DTextureFormat.BGRA
		private static const FORMAT_COMPRESSED	: String	= Context3DTextureFormat.COMPRESSED;

		private var _texture	: Texture		= null;
		private var _mipmap		: Boolean		= false;

		private var _bitmapData	: BitmapData	= null;
		private var _atf		: ByteArray		= null;
		private var _atfFormat	: uint			= 0;

		private var _width		: Number		= 0;
		private var _height		: Number		= 0;

		private var _update		: Boolean		= false;
		private var _resize		: Boolean		= false;

		public function get width() : uint
		{
			return _width;
		}

		public function get height() : uint
		{
			return _height;
		}

		public function TextureResource(width : int = 0, height : int = 0)
		{
			if (width != 0 && height != 0)
				setSize(width, height);
		}

		public function setSize(width : uint, height : uint) : void
		{
			//http://graphics.stanford.edu/~seander/bithacks.html#DetermineIfPowerOf2
			if (!(width && !(width & (width - 1)))
				|| !(height && !(height & (height - 1))))
			{
				throw new Error('The size must be a power of 2.');
			}

			_width	= width;
			_height	= height;
			_resize = true;
		}

		public function setContentFromBitmapData(bitmapData	: BitmapData,
								   				 mipmap		: Boolean,
												 downSample	: Boolean	= false) : void
		{
			var bitmapWidth		: uint 	= bitmapData.width;
			var bitmapHeight	: uint 	= bitmapData.height;
			var w				: int	= 0;
			var h				: int	= 0;

			if (downSample)
			{
				w = 1 << Math.floor(Math.log(bitmapWidth) * Math.LOG2E);
				h = 1 << Math.floor(Math.log(bitmapHeight) * Math.LOG2E);
			}
			else
			{
				w = 1 << Math.ceil(Math.log(bitmapWidth) * Math.LOG2E);
				h = 1 << Math.ceil(Math.log(bitmapHeight) * Math.LOG2E);
			}

			if (w > MAX_SIZE)
				w = MAX_SIZE;
			if (h > MAX_SIZE)
				h = MAX_SIZE;

			if (_bitmapData == null || _bitmapData.width != w || _bitmapData.height != h)
				_bitmapData	= new BitmapData(w, h, bitmapData.transparent, 0);

			if (w != bitmapWidth || h != bitmapHeight)
			{
				TMP_MATRIX.identity();
				TMP_MATRIX.scale(w / bitmapWidth, h / bitmapHeight);
				_bitmapData.draw(bitmapData, TMP_MATRIX);
			}
			else
			{
				_bitmapData.draw(bitmapData);
			}

			if (_texture
				&& (mipmap != _mipmap
					|| bitmapData.width != _width
					|| bitmapData.height != _height))
			{
				_texture.dispose();
				_texture = null;
			}

			_width = _bitmapData.width;
			_height = _bitmapData.height;

			_mipmap	= mipmap;
			_update	= true;
		}

		public function setContentFromATF(atf : ByteArray) : void
		{
			_atf			= atf;
			_update 		= true;

			atf.position 	= 6;

			_atfFormat 		= atf.readUnsignedByte() & 3;
			_width 			= 1 << atf.readUnsignedByte();
			_height 		= 1 << atf.readUnsignedByte();
			_mipmap 		= atf.readUnsignedByte() > 1;

			atf.position 	= 0;
		}

		public function getNativeTexture(context : Context3DResource) : TextureBase
		{
			if ((!_texture || _resize) && _width && _height)
			{
				_resize = false;

				if (_texture)
					_texture.dispose();

				_texture = context.createTexture(
					_width,
					_height,
					_atf && _atfFormat == 2 ? FORMAT_COMPRESSED : FORMAT_BGRA,
					_bitmapData == null && _atf == null
				);

				_update = true;
			}

			if (_update)
			{
				_update = false;

				uploadTextureWithMipMaps();
			}

			_atf = null;
			_bitmapData = null;

			if (_texture == null)
				throw new Error();

			return _texture;
		}

		private function uploadTextureWithMipMaps() : void
		{
			if (_bitmapData)
			{
				if (_mipmap)
				{
					var level 		: int 			= 0;
					var size		: int 			= _width > _height ? _width : _height;
					var transparent	: Boolean		= _bitmapData.transparent;
					var transform 	: Matrix 		= new Matrix();
					var tmp 		: BitmapData 	= new BitmapData(
						size,
						size,
						transparent,
						0
					);

					while (size >= 1)
					{
						tmp.draw(_bitmapData, transform, null, null, null, true);
						_texture.uploadFromBitmapData(tmp, level);

						transform.scale(.5, .5);
						level++;
						size >>= 1;
						if (tmp.transparent)
							tmp.fillRect(tmp.rect, 0);
					}

					tmp.dispose();
				}
				else
				{
					_texture.uploadFromBitmapData(_bitmapData, 0);
				}

				_bitmapData.dispose();
			}
			else if (_atf)
			{
				_texture.uploadCompressedTextureFromByteArray(_atf, 0, false);
			}
		}

		public function dispose() : void
		{
			if (_texture)
			{
				_texture.dispose();
				_texture = null;
			}
		}
	}
}

package aerys.minko.render.resource.texture
{
	import aerys.minko.render.resource.Context3DResource;

	import flash.display.BitmapData;
	import flash.display3D.Context3DTextureFormat;
	import flash.display3D.textures.Texture;
	import flash.display3D.textures.TextureBase;
	import flash.geom.Matrix;
	import flash.utils.ByteArray;

	/**
	 * @inheritdoc
	 * @author Jean-Marc Le Roux
	 *
	 */
	public final class TextureResource implements ITextureResource
	{
		private static const MAX_SIZE			: uint		= 2048;
		private static const TMP_MATRIX			: Matrix	= new Matrix();
		private static const FORMAT_BGRA		: String	= Context3DTextureFormat.BGRA
		private static const FORMAT_COMPRESSED	: String	= Context3DTextureFormat.COMPRESSED;

		private var _texture	: Texture		= null;
		private var _mipmap		: Boolean		= false;

		private var _bitmapData	: BitmapData	= null;
		private var _atf		: ByteArray		= null;
		private var _atfFormat	: uint			= 0;

		private var _width		: Number		= 0;
		private var _height		: Number		= 0;

		private var _update		: Boolean		= false;
		private var _resize		: Boolean		= false;

		public function get width() : uint
		{
			return _width;
		}

		public function get height() : uint
		{
			return _height;
		}

		public function TextureResource(width : int = 0, height : int = 0)
		{
			if (width != 0 && height != 0)
				setSize(width, height);
		}

		public function setSize(width : uint, height : uint) : void
		{
			//http://graphics.stanford.edu/~seander/bithacks.html#DetermineIfPowerOf2
			if (!(width && !(width & (width - 1)))
				|| !(height && !(height & (height - 1))))
			{
				throw new Error('The size must be a power of 2.');
			}

			_width	= width;
			_height	= height;
			_resize = true;
		}

		public function setContentFromBitmapData(bitmapData	: BitmapData,
								   				 mipmap		: Boolean,
												 downSample	: Boolean	= false) : void
		{
			var bitmapWidth		: uint 	= bitmapData.width;
			var bitmapHeight	: uint 	= bitmapData.height;
			var w				: int	= 0;
			var h				: int	= 0;

			if (downSample)
			{
				w = 1 << Math.floor(Math.log(bitmapWidth) * Math.LOG2E);
				h = 1 << Math.floor(Math.log(bitmapHeight) * Math.LOG2E);
			}
			else
			{
				w = 1 << Math.ceil(Math.log(bitmapWidth) * Math.LOG2E);
				h = 1 << Math.ceil(Math.log(bitmapHeight) * Math.LOG2E);
			}

			if (w > MAX_SIZE)
				w = MAX_SIZE;
			if (h > MAX_SIZE)
				h = MAX_SIZE;

			if (_bitmapData == null || _bitmapData.width != w || _bitmapData.height != h)
				_bitmapData	= new BitmapData(w, h, bitmapData.transparent, 0);

			if (w != bitmapWidth || h != bitmapHeight)
			{
				TMP_MATRIX.identity();
				TMP_MATRIX.scale(w / bitmapWidth, h / bitmapHeight);
				_bitmapData.draw(bitmapData, TMP_MATRIX);
			}
			else
			{
				_bitmapData.draw(bitmapData);
			}

			if (_texture
				&& (mipmap != _mipmap
					|| bitmapData.width != _width
					|| bitmapData.height != _height))
			{
				_texture.dispose();
				_texture = null;
			}

			_width = _bitmapData.width;
			_height = _bitmapData.height;

			_mipmap	= mipmap;
			_update	= true;
		}

		public function setContentFromATF(atf : ByteArray) : void
		{
			_atf			= atf;
			_bitmapData		= null;
			_update 		= true;

			atf.position 	= 6;

			_atfFormat 		= atf.readUnsignedByte() & 3;
			_width 			= 1 << atf.readUnsignedByte();
			_height 		= 1 << atf.readUnsignedByte();
			_mipmap 		= atf.readUnsignedByte() > 1;

			atf.position 	= 0;
		}

		public function getNativeTexture(context : Context3DResource) : TextureBase
		{
			if ((!_texture || _resize) && _width && _height)
			{
				_resize = false;

				if (_texture)
					_texture.dispose();

				_texture = context.createTexture(
					_width,
					_height,
					_atf && _atfFormat == 2 ? FORMAT_COMPRESSED : FORMAT_BGRA,
					_bitmapData == null && _atf == null
				);

				_update = true;
			}

			if (_update)
			{
				_update = false;

				uploadTextureWithMipMaps();
			}

			_atf = null;
			_bitmapData = null;

			if (_texture == null)
				throw new Error();

			return _texture;
		}

		private function uploadTextureWithMipMaps() : void
		{
			if (_bitmapData)
			{
				if (_mipmap)
				{
					var level 		: int 			= 0;
					var size		: int 			= _width > _height ? _width : _height;
					var transparent	: Boolean		= _bitmapData.transparent;
					var transform 	: Matrix 		= new Matrix();
					var tmp 		: BitmapData 	= new BitmapData(
						size,
						size,
						transparent,
						0
					);

					while (size >= 1)
					{
						tmp.draw(_bitmapData, transform, null, null, null, true);
						_texture.uploadFromBitmapData(tmp, level);

						transform.scale(.5, .5);
						level++;
						size >>= 1;
						if (tmp.transparent)
							tmp.fillRect(tmp.rect, 0);
					}

					tmp.dispose();
				}
				else
				{
					_texture.uploadFromBitmapData(_bitmapData, 0);
				}

				_bitmapData.dispose();
			}
			else if (_atf)
			{
				_texture.uploadCompressedTextureFromByteArray(_atf, 0, false);
			}
		}

		public function dispose() : void
		{
			if (_texture)
			{
				_texture.dispose();
				_texture = null;
			}
		}
	}
}

Fix for broken TextureResource.setContentForATF()