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

import com.esri.ags.components.IdentityManager;
import com.esri.ags.components.supportClasses.Credential;
import com.esri.ags.tasks.JSONTask;
import com.esri.builder.components.serviceBrowser.filters.GPTaskFilter;
import com.esri.builder.components.serviceBrowser.filters.GeocoderFilter;
import com.esri.builder.components.serviceBrowser.filters.INodeFilter;
import com.esri.builder.components.serviceBrowser.filters.MapLayerFilter;
import com.esri.builder.components.serviceBrowser.filters.MapServerFilter;
import com.esri.builder.components.serviceBrowser.filters.QueryableLayerFilter;
import com.esri.builder.components.serviceBrowser.filters.RouteLayerFilter;
import com.esri.builder.components.serviceBrowser.nodes.ServiceDirectoryRootNode;
import com.esri.builder.model.Model;
import com.esri.builder.model.PortalModel;
import com.esri.builder.supportClasses.LogUtil;

import flash.events.EventDispatcher;
import flash.net.URLVariables;

import mx.logging.ILogger;
import mx.logging.Log;
import mx.resources.ResourceManager;
import mx.rpc.Fault;
import mx.rpc.Responder;
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;
import mx.rpc.http.HTTPService;

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

    private static const DEFAULT_REQUEST_TIMEOUT_IN_SECONDS:Number = 10;

    private var serviceDirectoryBuildRequest:ServiceDirectoryBuildRequest;
    private var hasCrossDomain:Boolean;
    private var crossDomainRequest:HTTPService;
    private var credential:Credential;
    private var rootNode:ServiceDirectoryRootNode;
    private var currentNodeFilter:INodeFilter;
    private var isServiceSecured:Boolean;
    private var securityWarning:String;
    private var owningSystemURL:String;

    public function buildServiceDirectory(serviceDirectoryBuildRequest:ServiceDirectoryBuildRequest):void
    {
        if (Log.isInfo())
        {
            LOG.info("Building service directory");
        }

        this.serviceDirectoryBuildRequest = serviceDirectoryBuildRequest;

        try
        {
            checkCrossDomainBeforeBuildingDirectory();
        }
        catch (error:Error)
        {
            //TODO: handle error
        }
    }

    private function checkCrossDomainBeforeBuildingDirectory():void
    {
        if (Log.isDebug())
        {
            LOG.debug("Checking service URL crossdomain.xml");
        }

        crossDomainRequest = new HTTPService();
        crossDomainRequest.url = extractCrossDomainPolicyFileURL(serviceDirectoryBuildRequest.url);
        crossDomainRequest.addEventListener(ResultEvent.RESULT, crossDomainRequest_resultHandler);
        crossDomainRequest.addEventListener(FaultEvent.FAULT, crossDomainRequest_faultHandler);
        crossDomainRequest.requestTimeout = DEFAULT_REQUEST_TIMEOUT_IN_SECONDS;
        crossDomainRequest.send();

        function crossDomainRequest_resultHandler(event:ResultEvent):void
        {
            if (Log.isDebug())
            {
                LOG.debug("Found service crossdomain.xml");
            }

            crossDomainRequest.removeEventListener(ResultEvent.RESULT, crossDomainRequest_resultHandler);
            crossDomainRequest.removeEventListener(FaultEvent.FAULT, crossDomainRequest_faultHandler);
            crossDomainRequest = null;
            hasCrossDomain = true;

            getServiceInfo();
        }

        function crossDomainRequest_faultHandler(event:FaultEvent):void
        {
            if (Log.isDebug())
            {
                LOG.debug("Could not find service crossdomain.xml");
            }

            crossDomainRequest.removeEventListener(ResultEvent.RESULT, crossDomainRequest_resultHandler);
            crossDomainRequest.removeEventListener(FaultEvent.FAULT, crossDomainRequest_faultHandler);
            crossDomainRequest = null;
            hasCrossDomain = false;

            getServiceInfo();
        }
    }

    private function extractCrossDomainPolicyFileURL(url:String):String
    {
        var baseURL:RegExp = /(https?:\/\/)([^\/]+\/)/
        var baseURLMatch:Array = url.match(baseURL);
        return baseURLMatch[0] + 'crossdomain.xml';
    }

    private function getServiceInfo():void
    {
        var serviceInfoRequest:JSONTask = new JSONTask();
        serviceInfoRequest.url = extractServiceInfoURL(serviceDirectoryBuildRequest.url);
        const param:URLVariables = new URLVariables();
        param.f = 'json';
        serviceInfoRequest.execute(param, new Responder(serviceInfoRequest_resultHandler, serviceInfoRequest_faultHandler));

        function serviceInfoRequest_resultHandler(serverInfo:Object):void
        {
            owningSystemURL = serverInfo.owningSystemUrl;
            if (PortalModel.getInstance().hasSameOrigin(owningSystemURL))
            {
                if (PortalModel.getInstance().portal.signedIn)
                {
                    IdentityManager.instance.getCredential(
                        serviceDirectoryBuildRequest.url, false,
                        new Responder(getCredential_successHandler, getCredential_faultHandler));

                    function getCredential_successHandler(credential:Credential):void
                    {
                        checkIfServiceIsSecure(serverInfo);
                    }

                    function getCredential_faultHandler(fault:Fault):void
                    {
                        checkIfServiceIsSecure(serverInfo);
                    }
                }
                else
                {
                    var credential:Credential = IdentityManager.instance.findCredential(serviceDirectoryBuildRequest.url);
                    if (credential)
                    {
                        credential.destroy();
                    }
                    checkIfServiceIsSecure(serverInfo);
                }
            }
            else
            {
                checkIfServiceIsSecure(serverInfo);
            }
        }

        function serviceInfoRequest_faultHandler(fault:Fault):void
        {
            checkIfServiceIsSecure(null);
        }
    }

    private function checkIfServiceIsSecure(serverInfo:Object):void
    {
        if (Log.isInfo())
        {
            LOG.info("Checking if service is secure");
        }

        if (serverInfo)
        {
            isServiceSecured = (serverInfo.currentVersion >= 10.01
                && serverInfo.authInfo
                && serverInfo.authInfo.isTokenBasedSecurity
                && (serverInfo.authInfo.tokenServicesUrl || serverInfo.authInfo.tokenServiceUrl));

            if (isServiceSecured)
            {
                if (Log.isDebug())
                {
                    LOG.debug("Service is secure");
                }

                if (Log.isDebug())
                {
                    LOG.debug("Checking token service crossdomain.xml");
                }

                const tokenServiceCrossDomainRequest:HTTPService = new HTTPService();
                const tokenServiceURL:String = serverInfo.authInfo.tokenServicesUrl || serverInfo.authInfo.tokenServiceUrl;
                tokenServiceCrossDomainRequest.url = extractCrossDomainPolicyFileURL(tokenServiceURL);
                tokenServiceCrossDomainRequest.resultFormat = HTTPService.RESULT_FORMAT_E4X;
                tokenServiceCrossDomainRequest.addEventListener(ResultEvent.RESULT, tokenServiceSecurityRequest_resultHandler);
                tokenServiceCrossDomainRequest.addEventListener(FaultEvent.FAULT, tokenServiceSecurityRequest_faultHandler);
                tokenServiceCrossDomainRequest.send();

                function tokenServiceSecurityRequest_resultHandler(event:ResultEvent):void
                {
                    if (Log.isDebug())
                    {
                        LOG.debug("Found token service crossdomain.xml");
                    }

                    tokenServiceCrossDomainRequest.removeEventListener(ResultEvent.RESULT, tokenServiceSecurityRequest_resultHandler);
                    tokenServiceCrossDomainRequest.removeEventListener(FaultEvent.FAULT, tokenServiceSecurityRequest_faultHandler);

                    const startsWithHTTPS:RegExp = /^https/;
                    if (startsWithHTTPS.test(tokenServiceCrossDomainRequest.url))
                    {
                        const tokenServiceCrossDomainXML:XML = event.result as XML;
                        const hasSecurityEnabled:Boolean =
                            tokenServiceCrossDomainXML.child("allow-access-from").(attribute("secure") == "false").length() == 0
                            || tokenServiceCrossDomainXML.child("allow-http-request-headers-from").(attribute("secure") == "false").length() == 0;

                        if (hasSecurityEnabled
                            && !startsWithHTTPS.test(Model.instance.appDir.url))
                        {
                            securityWarning = ResourceManager.getInstance().getString('BuilderStrings', 'serviceBrowser.secureTokenServiceWithSecureCrossDomain');
                        }
                    }
                }

                function tokenServiceSecurityRequest_faultHandler(event:FaultEvent):void
                {
                    if (Log.isDebug())
                    {
                        LOG.debug("Could not find token service crossdomain.xml");
                    }

                    tokenServiceCrossDomainRequest.removeEventListener(ResultEvent.RESULT, tokenServiceSecurityRequest_resultHandler);
                    tokenServiceCrossDomainRequest.removeEventListener(FaultEvent.FAULT, tokenServiceSecurityRequest_faultHandler);
                    securityWarning = ResourceManager.getInstance().getString('BuilderStrings', 'serviceBrowser.secureTokenServiceMissingCrossDomain');
                }
            }

            //continue with building service directory
            startBuildingDirectory();
        }
        else
        {
            //continue with building service directory
            startBuildingDirectory();
        }
    }

    private function extractServiceInfoURL(url:String):String
    {
        return url.replace('/rest/services', '/rest/info');
    }

    private function startBuildingDirectory():void
    {
        if (Log.isInfo())
        {
            LOG.info('Building serviced directory {0}', serviceDirectoryBuildRequest.url);
        }

        const servicesDirectoryURL:RegExp = /.+\/rest\/services\/?/;
        const serviceURLRootMatch:Array = servicesDirectoryURL.exec(serviceDirectoryBuildRequest.url);

        if (!serviceURLRootMatch)
        {
            return;
        }

        currentNodeFilter = filterFromSearchType(serviceDirectoryBuildRequest.searchType);
        rootNode = new ServiceDirectoryRootNode(serviceDirectoryBuildRequest.url, currentNodeFilter);
        rootNode.addEventListener(URLNodeTraversalEvent.END_REACHED, rootNode_completeHandler);
        rootNode.addEventListener(FaultEvent.FAULT, rootNode_faultHandler);
        credential = IdentityManager.instance.findCredential(serviceURLRootMatch[0]);
        if (credential)
        {
            rootNode.token = credential.token;
        }
        rootNode.loadChildren();
    }

    private function filterFromSearchType(searchType:String):INodeFilter
    {
        if (searchType == ServiceDirectoryBuildRequest.QUERYABLE_LAYER_SEARCH)
        {
            return new QueryableLayerFilter();
        }
        else if (searchType == ServiceDirectoryBuildRequest.GEOPROCESSING_TASK_SEARCH)
        {
            return new GPTaskFilter();
        }
        else if (searchType == ServiceDirectoryBuildRequest.GEOCODER_SEARCH)
        {
            return new GeocoderFilter();
        }
        else if (searchType == ServiceDirectoryBuildRequest.ROUTE_LAYER_SEARCH)
        {
            return new RouteLayerFilter();
        }
        else if (searchType == ServiceDirectoryBuildRequest.MAP_SERVER_SEARCH)
        {
            return new MapServerFilter();
        }
        else //MAP LAYER SEARCH
        {
            return new MapLayerFilter();
        }
    }

    private function rootNode_completeHandler(event:URLNodeTraversalEvent):void
    {
        if (Log.isInfo())
        {
            LOG.info("Finished building service directory");
        }

        dispatchEvent(
            new ServiceDirectoryBuilderEvent(ServiceDirectoryBuilderEvent.COMPLETE,
                                             new ServiceDirectoryInfo(rootNode,
                                                                      event.urlNodes,
                                                                      currentNodeFilter,
                                                                      hasCrossDomain,
                                                                      isServiceSecured,
                                                                      securityWarning,
                                                                      owningSystemURL)));
    }

    protected function rootNode_faultHandler(event:FaultEvent):void
    {
        if (Log.isInfo())
        {
            LOG.info("Could not build service directory");
        }

        dispatchEvent(event);
    }
}
}

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

import com.esri.ags.components.IdentityManager;
import com.esri.ags.components.supportClasses.Credential;
import com.esri.ags.tasks.JSONTask;
import com.esri.builder.components.serviceBrowser.filters.GPTaskFilter;
import com.esri.builder.components.serviceBrowser.filters.GeocoderFilter;
import com.esri.builder.components.serviceBrowser.filters.INodeFilter;
import com.esri.builder.components.serviceBrowser.filters.MapLayerFilter;
import com.esri.builder.components.serviceBrowser.filters.MapServerFilter;
import com.esri.builder.components.serviceBrowser.filters.QueryableLayerFilter;
import com.esri.builder.components.serviceBrowser.filters.RouteLayerFilter;
import com.esri.builder.components.serviceBrowser.nodes.ServiceDirectoryRootNode;
import com.esri.builder.model.Model;
import com.esri.builder.model.PortalModel;
import com.esri.builder.supportClasses.LogUtil;

import flash.events.EventDispatcher;
import flash.net.URLVariables;

import mx.logging.ILogger;
import mx.logging.Log;
import mx.resources.ResourceManager;
import mx.rpc.Fault;
import mx.rpc.Responder;
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;
import mx.rpc.http.HTTPService;

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

    private static const DEFAULT_REQUEST_TIMEOUT_IN_SECONDS:Number = 10;

    private var serviceDirectoryBuildRequest:ServiceDirectoryBuildRequest;
    private var hasCrossDomain:Boolean;
    private var crossDomainRequest:HTTPService;
    private var credential:Credential;
    private var rootNode:ServiceDirectoryRootNode;
    private var currentNodeFilter:INodeFilter;
    private var isServiceSecured:Boolean;
    private var securityWarning:String;
    private var owningSystemURL:String;

    public function buildServiceDirectory(serviceDirectoryBuildRequest:ServiceDirectoryBuildRequest):void
    {
        if (Log.isInfo())
        {
            LOG.info("Building service directory");
        }

        this.serviceDirectoryBuildRequest = serviceDirectoryBuildRequest;

        try
        {
            checkCrossDomainBeforeBuildingDirectory();
        }
        catch (error:Error)
        {
            //TODO: handle error
        }
    }

    private function checkCrossDomainBeforeBuildingDirectory():void
    {
        if (Log.isDebug())
        {
            LOG.debug("Checking service URL crossdomain.xml");
        }

        crossDomainRequest = new HTTPService();
        crossDomainRequest.url = extractCrossDomainPolicyFileURL(serviceDirectoryBuildRequest.url);
        crossDomainRequest.addEventListener(ResultEvent.RESULT, crossDomainRequest_resultHandler);
        crossDomainRequest.addEventListener(FaultEvent.FAULT, crossDomainRequest_faultHandler);
        crossDomainRequest.requestTimeout = DEFAULT_REQUEST_TIMEOUT_IN_SECONDS;
        crossDomainRequest.send();

        function crossDomainRequest_resultHandler(event:ResultEvent):void
        {
            if (Log.isDebug())
            {
                LOG.debug("Found service crossdomain.xml");
            }

            crossDomainRequest.removeEventListener(ResultEvent.RESULT, crossDomainRequest_resultHandler);
            crossDomainRequest.removeEventListener(FaultEvent.FAULT, crossDomainRequest_faultHandler);
            crossDomainRequest = null;
            hasCrossDomain = true;

            getServiceInfo();
        }

        function crossDomainRequest_faultHandler(event:FaultEvent):void
        {
            if (Log.isDebug())
            {
                LOG.debug("Could not find service crossdomain.xml");
            }

            crossDomainRequest.removeEventListener(ResultEvent.RESULT, crossDomainRequest_resultHandler);
            crossDomainRequest.removeEventListener(FaultEvent.FAULT, crossDomainRequest_faultHandler);
            crossDomainRequest = null;
            hasCrossDomain = false;

            getServiceInfo();
        }
    }

    private function extractCrossDomainPolicyFileURL(url:String):String
    {
        var baseURL:RegExp = /(https?:\/\/)([^\/]+\/)/
        var baseURLMatch:Array = url.match(baseURL);
        return baseURLMatch[0] + 'crossdomain.xml';
    }

    private function getServiceInfo():void
    {
        var serviceInfoRequest:JSONTask = new JSONTask();
        serviceInfoRequest.url = extractServiceInfoURL(serviceDirectoryBuildRequest.url);
        const param:URLVariables = new URLVariables();
        param.f = 'json';
        serviceInfoRequest.execute(param, new Responder(serviceInfoRequest_resultHandler, serviceInfoRequest_faultHandler));

        function serviceInfoRequest_resultHandler(serverInfo:Object):void
        {
            owningSystemURL = serverInfo.owningSystemUrl;
            if (PortalModel.getInstance().hasSameOrigin(owningSystemURL))
            {
                if (PortalModel.getInstance().portal.signedIn)
                {
                    IdentityManager.instance.getCredential(
                        serviceDirectoryBuildRequest.url, false,
                        new Responder(getCredential_successHandler, getCredential_faultHandler));

                    function getCredential_successHandler(credential:Credential):void
                    {
                        checkIfServiceIsSecure(serverInfo);
                    }

                    function getCredential_faultHandler(fault:Fault):void
                    {
                        checkIfServiceIsSecure(serverInfo);
                    }
                }
                else
                {
                    var credential:Credential = IdentityManager.instance.findCredential(serviceDirectoryBuildRequest.url);
                    if (credential)
                    {
                        credential.destroy();
                    }
                    checkIfServiceIsSecure(serverInfo);
                }
            }
            else
            {
                checkIfServiceIsSecure(serverInfo);
            }
        }

        function serviceInfoRequest_faultHandler(fault:Fault):void
        {
            checkIfServiceIsSecure(null);
        }
    }

    private function checkIfServiceIsSecure(serverInfo:Object):void
    {
        if (Log.isInfo())
        {
            LOG.info("Checking if service is secure");
        }

        if (serverInfo)
        {
            isServiceSecured = (serverInfo.currentVersion >= 10.01
                && serverInfo.authInfo
                && serverInfo.authInfo.isTokenBasedSecurity
                && (serverInfo.authInfo.tokenServicesUrl || serverInfo.authInfo.tokenServiceUrl));

            if (isServiceSecured)
            {
                if (Log.isDebug())
                {
                    LOG.debug("Service is secure");
                }

                if (Log.isDebug())
                {
                    LOG.debug("Checking token service crossdomain.xml");
                }

                const tokenServiceCrossDomainRequest:HTTPService = new HTTPService();
                const tokenServiceURL:String = serverInfo.authInfo.tokenServicesUrl || serverInfo.authInfo.tokenServiceUrl;
                tokenServiceCrossDomainRequest.url = extractCrossDomainPolicyFileURL(tokenServiceURL);
                tokenServiceCrossDomainRequest.resultFormat = HTTPService.RESULT_FORMAT_E4X;
                tokenServiceCrossDomainRequest.addEventListener(ResultEvent.RESULT, tokenServiceSecurityRequest_resultHandler);
                tokenServiceCrossDomainRequest.addEventListener(FaultEvent.FAULT, tokenServiceSecurityRequest_faultHandler);
                tokenServiceCrossDomainRequest.send();

                function tokenServiceSecurityRequest_resultHandler(event:ResultEvent):void
                {
                    if (Log.isDebug())
                    {
                        LOG.debug("Found token service crossdomain.xml");
                    }

                    tokenServiceCrossDomainRequest.removeEventListener(ResultEvent.RESULT, tokenServiceSecurityRequest_resultHandler);
                    tokenServiceCrossDomainRequest.removeEventListener(FaultEvent.FAULT, tokenServiceSecurityRequest_faultHandler);

                    const startsWithHTTPS:RegExp = /^https/;
                    if (startsWithHTTPS.test(tokenServiceCrossDomainRequest.url))
                    {
                        const tokenServiceCrossDomainXML:XML = event.result as XML;
                        const hasSecurityEnabled:Boolean =
                            tokenServiceCrossDomainXML.child("allow-access-from").(attribute("secure") == "false").length() == 0
                            || tokenServiceCrossDomainXML.child("allow-http-request-headers-from").(attribute("secure") == "false").length() == 0;

                        if (hasSecurityEnabled
                            && !startsWithHTTPS.test(Model.instance.appDir.url))
                        {
                            securityWarning = ResourceManager.getInstance().getString('BuilderStrings', 'serviceBrowser.secureTokenServiceWithSecureCrossDomain');
                        }
                    }
                }

                function tokenServiceSecurityRequest_faultHandler(event:FaultEvent):void
                {
                    if (Log.isDebug())
                    {
                        LOG.debug("Could not find token service crossdomain.xml");
                    }

                    tokenServiceCrossDomainRequest.removeEventListener(ResultEvent.RESULT, tokenServiceSecurityRequest_resultHandler);
                    tokenServiceCrossDomainRequest.removeEventListener(FaultEvent.FAULT, tokenServiceSecurityRequest_faultHandler);
                    securityWarning = ResourceManager.getInstance().getString('BuilderStrings', 'serviceBrowser.secureTokenServiceMissingCrossDomain');
                }
            }

            //continue with building service directory
            startBuildingDirectory();
        }
        else
        {
            //continue with building service directory
            startBuildingDirectory();
        }
    }

    private function extractServiceInfoURL(url:String):String
    {
        return url.replace(/\/rest\/services.*/, '/rest/info');
    }

    private function startBuildingDirectory():void
    {
        if (Log.isInfo())
        {
            LOG.info('Building serviced directory {0}', serviceDirectoryBuildRequest.url);
        }

        const servicesDirectoryURL:RegExp = /.+\/rest\/services\/?/;
        const serviceURLRootMatch:Array = servicesDirectoryURL.exec(serviceDirectoryBuildRequest.url);

        if (!serviceURLRootMatch)
        {
            return;
        }

        currentNodeFilter = filterFromSearchType(serviceDirectoryBuildRequest.searchType);
        rootNode = new ServiceDirectoryRootNode(serviceDirectoryBuildRequest.url, currentNodeFilter);
        rootNode.addEventListener(URLNodeTraversalEvent.END_REACHED, rootNode_completeHandler);
        rootNode.addEventListener(FaultEvent.FAULT, rootNode_faultHandler);
        credential = IdentityManager.instance.findCredential(serviceURLRootMatch[0]);
        if (credential)
        {
            rootNode.token = credential.token;
        }
        rootNode.loadChildren();
    }

    private function filterFromSearchType(searchType:String):INodeFilter
    {
        if (searchType == ServiceDirectoryBuildRequest.QUERYABLE_LAYER_SEARCH)
        {
            return new QueryableLayerFilter();
        }
        else if (searchType == ServiceDirectoryBuildRequest.GEOPROCESSING_TASK_SEARCH)
        {
            return new GPTaskFilter();
        }
        else if (searchType == ServiceDirectoryBuildRequest.GEOCODER_SEARCH)
        {
            return new GeocoderFilter();
        }
        else if (searchType == ServiceDirectoryBuildRequest.ROUTE_LAYER_SEARCH)
        {
            return new RouteLayerFilter();
        }
        else if (searchType == ServiceDirectoryBuildRequest.MAP_SERVER_SEARCH)
        {
            return new MapServerFilter();
        }
        else //MAP LAYER SEARCH
        {
            return new MapLayerFilter();
        }
    }

    private function rootNode_completeHandler(event:URLNodeTraversalEvent):void
    {
        if (Log.isInfo())
        {
            LOG.info("Finished building service directory");
        }

        dispatchEvent(
            new ServiceDirectoryBuilderEvent(ServiceDirectoryBuilderEvent.COMPLETE,
                                             new ServiceDirectoryInfo(rootNode,
                                                                      event.urlNodes,
                                                                      currentNodeFilter,
                                                                      hasCrossDomain,
                                                                      isServiceSecured,
                                                                      securityWarning,
                                                                      owningSystemURL)));
    }

    protected function rootNode_faultHandler(event:FaultEvent):void
    {
        if (Log.isInfo())
        {
            LOG.info("Could not build service directory");
        }

        dispatchEvent(event);
    }
}
}

Trim trailing characters when extracting the server info URL.


package org.flexunit.experimental.theories.internals.cases
{
	import org.flexunit.Assert;
	import org.flexunit.experimental.theories.internals.ParameterizedAssertionError;

	public class ParameterizedAssertionErrorCase
	{
		//TODO: Ensure that these tests and this test case are being implemented correctly.
		//It is currently impossible to test the stringValueOf function.

		[Test(description="Ensure that the ParameterizedAssertionError constructor is correctly assigning parameter values")]
		public function constructorTest():void {
			var targetException:Error = new Error();
			var methodName:String = "methodName";
			var params:Array = new Array("valueOne", "valueTwo");

			var parameterizedAssertionError:ParameterizedAssertionError = new ParameterizedAssertionError(targetException, methodName, "valueOne", "valueTwo");

			var message:String = methodName + " " + params.join( ", " );
			Assert.assertEquals( message, parameterizedAssertionError.message);
			Assert.assertEquals( targetException, parameterizedAssertionError.targetException );
		}

		[Test(description="Ensure that the join function is correctly joining the delimiter to the other parameters")]
		public function joinTest():void {
			var delimiter:String = ", ";
			var params:Array = new Array("valueOne", "valueTwo", "valueThree");

			var message:String = params.join( delimiter );
			Assert.assertEquals( message, ParameterizedAssertionError.join(delimiter, "valueOne", "valueTwo", "valueThree") );
		}
	}
}

package org.flexunit.experimental.theories.internals.cases
{
	import org.flexunit.Assert;
	import org.flexunit.experimental.theories.internals.ParameterizedAssertionError;

	public class ParameterizedAssertionErrorCase
	{
		//TODO: Ensure that these tests and this test case are being implemented correctly.
		//It is currently impossible to test the stringValueOf function.

		[Ignore("Currently Ignoring Test as this functionality is under investigation due to Max stack overflow issue")]
		[Test(description="Ensure that the ParameterizedAssertionError constructor is correctly assigning parameter values")]
		public function constructorTest():void {
			var targetException:Error = new Error();
			var methodName:String = "methodName";
			var params:Array = new Array("valueOne", "valueTwo");

			var parameterizedAssertionError:ParameterizedAssertionError = new ParameterizedAssertionError(targetException, methodName, "valueOne", "valueTwo");

			var message:String = methodName + " " + params.join( ", " );
			Assert.assertEquals( message, parameterizedAssertionError.message);
			Assert.assertEquals( targetException, parameterizedAssertionError.targetException );
		}

		[Test(description="Ensure that the join function is correctly joining the delimiter to the other parameters")]
		public function joinTest():void {
			var delimiter:String = ", ";
			var params:Array = new Array("valueOne", "valueTwo", "valueThree");

			var message:String = params.join( delimiter );
			Assert.assertEquals( message, ParameterizedAssertionError.join(delimiter, "valueOne", "valueTwo", "valueThree") );
		}
	}
}

Set test to ignore while stack trace is still under investigation


////////////////////////////////////////////////////////////////////////////////
//
//  Licensed to the Apache Software Foundation (ASF) under one or more
//  contributor license agreements.  See the NOTICE file distributed with
//  this work for additional information regarding copyright ownership.
//  The ASF licenses this file to You under the Apache License, Version 2.0
//  (the "License"); you may not use this file except in compliance with
//  the License.  You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
package org.apache.flex.html.staticControls.beads
{
	import flash.display.DisplayObject;
	import flash.display.DisplayObjectContainer;

	import org.apache.flex.core.CSSTextField;
	import org.apache.flex.core.IBead;
	import org.apache.flex.core.IStrand;
	import org.apache.flex.core.ITextBead;
	import org.apache.flex.core.ITextModel;
	import org.apache.flex.events.Event;

	public class TextFieldBeadBase implements IBead, ITextBead
	{
		public function TextFieldBeadBase()
		{
			_textField = new CSSTextField();
		}

		private var _textField:CSSTextField;

		public function get textField() : CSSTextField
		{
			return _textField;
		}

		private var _textModel:ITextModel;

		public function get textModel() : ITextModel
		{
			return _textModel;
		}

		private var _strand:IStrand;

		public function set strand(value:IStrand):void
		{
			_strand = value;
			_textModel = value.getBeadByType(ITextModel) as ITextModel;
			textModel.addEventListener("textChange", textChangeHandler);
			textModel.addEventListener("htmlChange", htmlChangeHandler);
			textModel.addEventListener("widthChanged", sizeChangeHandler);
			textModel.addEventListener("heightChanged", sizeChangeHandler);
			DisplayObjectContainer(value).addChild(_textField);
			sizeChangeHandler(null);
			if (textModel.text !== null)
				text = textModel.text;
			if (textModel.html !== null)
				html = textModel.html;
		}

		public function get strand() : IStrand
		{
			return _strand;
		}

		public function get text():String
		{
			return _textField.text;
		}
		public function set text(value:String):void
		{
			_textField.text = value;
		}

		public function get html():String
		{
			return _textField.htmlText;
		}

		public function set html(value:String):void
		{
			_textField.htmlText = value;
		}

		private function textChangeHandler(event:Event):void
		{
			text = textModel.text;
		}

		private function htmlChangeHandler(event:Event):void
		{
			html = textModel.html;
		}

		private function sizeChangeHandler(event:Event):void
		{
			textField.width = DisplayObject(_strand).width;
			textField.height = DisplayObject(_strand).height;
		}
	}
}

////////////////////////////////////////////////////////////////////////////////
//
//  Licensed to the Apache Software Foundation (ASF) under one or more
//  contributor license agreements.  See the NOTICE file distributed with
//  this work for additional information regarding copyright ownership.
//  The ASF licenses this file to You under the Apache License, Version 2.0
//  (the "License"); you may not use this file except in compliance with
//  the License.  You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
package org.apache.flex.html.staticControls.beads
{
	import flash.display.DisplayObject;
	import flash.display.DisplayObjectContainer;

	import org.apache.flex.core.CSSTextField;
	import org.apache.flex.core.IBead;
	import org.apache.flex.core.IStrand;
	import org.apache.flex.core.ITextBead;
	import org.apache.flex.core.ITextModel;
	import org.apache.flex.events.Event;

	public class TextFieldBeadBase implements IBead, ITextBead
	{
		public function TextFieldBeadBase()
		{
			_textField = new CSSTextField();
		}

		private var _textField:CSSTextField;

		public function get textField() : CSSTextField
		{
			return _textField;
		}

		private var _textModel:ITextModel;

		public function get textModel() : ITextModel
		{
			return _textModel;
		}

		private var _strand:IStrand;

		public function set strand(value:IStrand):void
		{
			_strand = value;
			_textModel = value.getBeadByType(ITextModel) as ITextModel;
			textModel.addEventListener("textChange", textChangeHandler);
			textModel.addEventListener("htmlChange", htmlChangeHandler);
			textModel.addEventListener("widthChanged", sizeChangeHandler);
			textModel.addEventListener("heightChanged", sizeChangeHandler);
			DisplayObjectContainer(value).addChild(_textField);
			sizeChangeHandler(null);
			if (textModel.text !== null)
				text = textModel.text;
			if (textModel.html !== null)
				html = textModel.html;
		}

		public function get strand() : IStrand
		{
			return _strand;
		}

		public function get text():String
		{
			return _textField.text;
		}
		public function set text(value:String):void
		{
            if (value == null)
                value == "";
			_textField.text = value;
		}

		public function get html():String
		{
			return _textField.htmlText;
		}

		public function set html(value:String):void
		{
			_textField.htmlText = value;
		}

		private function textChangeHandler(event:Event):void
		{
			text = textModel.text;
		}

		private function htmlChangeHandler(event:Event):void
		{
			html = textModel.html;
		}

		private function sizeChangeHandler(event:Event):void
		{
			textField.width = DisplayObject(_strand).width;
			textField.height = DisplayObject(_strand).height;
		}
	}
}

handle null to prevent RTE

handle null to prevent RTE


package asx.array {

  /**
   * Injects the memo value into an iterator function that is applied to every item in the array.
   *
   * @param memo Initial value to pass to the iterator
   * @param array Array of values to apply the iterator to
   * @param iterator Function to be applied to each value. Function signature must match one of:
   *

   *
function(memo:Object, value:Object):Object;

   *
function(memo:Object, value:Object, i:int):Object;

   *
function(memo:Object, value:Object, i:int, a:Array):Object;

   *

   * @return result of applying the memo and value of each array item to the iterator
   * @example Summing an array of numbers can be implemented with inject.
   *  <listing version="3.0">
   *  var numbers:Array = [1, 2, 3, 4, 5];
   *  var sum:Number = inject(0, function(total:Number, n:Number):Number {
   *    return total + n;
   *  });
   *  </listing>
   */
  public function inject(memo:Object, array:Array, iterator:Function):Object {
    array.forEach(function(value:Object, i:int, a:Array):void {
      memo = iterator.apply(null, [memo, value, i, a].slice(0, Math.max(2, iterator.length)));
    });
    return memo;
  }
}

package asx.array {

  /**
   * Injects the memo value into an iterator function that is applied to every item in the array.
   *
   * @param memo Initial value to pass to the iterator
   * @param array Array of values to apply the iterator to
   * @param iterator Function to be applied to each value. Function signature must match one of:
   *

   *
function(memo:Object, value:Object):Object;

   *
function(memo:Object, value:Object, i:int):Object;

   *
function(memo:Object, value:Object, i:int, a:Array):Object;

   *

   * @return result of applying the memo and value of each array item to the iterator
   * @example Summing an array of numbers can be implemented with inject.
   *  <listing version="3.0">
   *  var numbers:Array = [1, 2, 3, 4, 5];
   *  var sum:Number = inject(0, numbers, function(total:Number, n:Number):Number {
   *    return total + n;
   *  });
   *  </listing>
   */
  public function inject(memo:Object, array:Array, iterator:Function):Object {
    array.forEach(function(value:Object, i:int, a:Array):void {
      memo = iterator.apply(null, [memo, value, i, a].slice(0, Math.max(2, iterator.length)));
    });
    return memo;
  }
}

correct example for asx.array.inject

correct example for asx.array.inject


package dolly {

import dolly.core.dolly_internal;
import dolly.data.CloneableClass;

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

use namespace dolly_internal;

public class CloningOfCloneableClassTest {

	private var cloneableClass:CloneableClass;
	private var cloneableClassType:Type;

	[Before]
	public function before():void {
		cloneableClass = new CloneableClass();
		cloneableClass.property1 = "property1 value";
		cloneableClass.writableField1 = "writableField1 value";
		cloneableClassType = Type.forInstance(cloneableClass);
	}

	[After]
	public function after():void {
		cloneableClass = null;
		cloneableClassType = null;
	}

	[Test]
	public function findingAllWritableFieldsForType():void {
		const writableFields:Vector.<Field> = Cloner.findAllWritableFieldsForType(cloneableClassType);
		assertNotNull(writableFields);
		assertEquals(4, writableFields.length);
	}

	[Test]
	public function cloningByCloner():void {
		const clone:CloneableClass = Cloner.clone(cloneableClass);

		assertNotNull(clone);
		assertNotNull(clone.property1);
		assertEquals(clone.property1, cloneableClass.property1);
		assertNotNull(clone.writableField1);
		assertEquals(clone.writableField1, cloneableClass.writableField1);
	}

	[Test]
	public function cloningByCloneFunction():void {
		const classLevelClone:CloneableClass = clone(cloneableClass);

		assertNotNull(classLevelClone);
		assertNotNull(classLevelClone.property1);
		assertEquals(classLevelClone.property1, cloneableClass.property1);
		assertNotNull(classLevelClone.writableField1);
		assertEquals(classLevelClone.writableField1, cloneableClass.writableField1);
	}
}
}

package dolly {

import dolly.core.dolly_internal;
import dolly.data.CloneableClass;

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

use namespace dolly_internal;

public class CloningOfCloneableClassTest {

	private var cloneableClass:CloneableClass;
	private var cloneableClassType:Type;

	[Before]
	public function before():void {
		cloneableClass = new CloneableClass();
		cloneableClass.property1 = "property1 value";
		cloneableClass.writableField1 = "writableField1 value";
		cloneableClassType = Type.forInstance(cloneableClass);
	}

	[After]
	public function after():void {
		cloneableClass = null;
		cloneableClassType = null;
	}

	[Test]
	public function findingAllWritableFieldsForType():void {
		const writableFields:Vector.<Field> = Cloner.findAllWritableFieldsForType(cloneableClassType);
		assertNotNull(writableFields);
		assertEquals(2, writableFields.length);
	}

	[Test]
	public function cloningByCloner():void {
		const clone:CloneableClass = Cloner.clone(cloneableClass);

		assertNotNull(clone);
		assertNotNull(clone.property1);
		assertEquals(clone.property1, cloneableClass.property1);
		assertNotNull(clone.writableField1);
		assertEquals(clone.writableField1, cloneableClass.writableField1);
	}

	[Test]
	public function cloningByCloneFunction():void {
		const classLevelClone:CloneableClass = clone(cloneableClass);

		assertNotNull(classLevelClone);
		assertNotNull(classLevelClone.property1);
		assertEquals(classLevelClone.property1, cloneableClass.property1);
		assertNotNull(classLevelClone.writableField1);
		assertEquals(classLevelClone.writableField1, cloneableClass.writableField1);
	}
}
}

Change expected value to 2 in test.

Change expected value to 2 in test.


#if __SWF_VERSION__ == 5
// create a _global object, since it doesn't have one, these are ver 6 values
_global = new_empty_object ();
_global.ASSetNative = ASSetNative;
_global.ASSetNativeAccessor = ASSetNativeAccessor;
_global.ASSetPropFlags = ASSetPropFlags;
_global.ASconstructor = ASconstructor;
_global.ASnative = ASnative;
_global.Accessibility = Accessibility;
_global.Array = Array;
_global.AsBroadcaster = AsBroadcaster;
_global.AsSetupError = AsSetupError;
_global.Boolean = Boolean;
_global.Button = Button;
_global.Camera = Camera;
_global.Color = Color;
_global.ContextMenu = ContextMenu;
_global.ContextMenuItem = ContextMenuItem;
_global.Date = Date;
_global.Error = Error;
_global.Function = Function;
_global.Infinity = Infinity;
_global.Key = Key;
_global.LoadVars = LoadVars;
_global.LocalConnection = LocalConnection;
_global.Math = Math;
_global.Microphone = Microphone;
_global.Mouse = Mouse;
_global.MovieClip = MovieClip;
_global.MovieClipLoader = MovieClipLoader;
_global.NaN = NaN;
_global.NetConnection = NetConnection;
_global.NetStream = NetStream;
_global.Number = Number;
_global.Object = Object;
_global.PrintJob = PrintJob;
_global.RemoteLSOUsage = RemoteLSOUsage;
_global.Selection = Selection;
_global.SharedObject = SharedObject;
_global.Sound = Sound;
_global.Stage = Stage;
_global.String = String;
_global.System = System;
_global.TextField = TextField;
_global.TextFormat = TextFormat;
_global.TextSnapshot = TextSnapshot;
_global.Video = Video;
_global.XML = XML;
_global.XMLNode = XMLNode;
_global.XMLSocket = XMLSocket;
_global.clearInterval = clearInterval;
_global.clearTimeout = clearTimeout;
_global.enableDebugConsole = enableDebugConsole;
_global.escape = escape;
_global.flash = flash;
_global.isFinite = isFinite;
_global.isNaN = isNaN;
_global.o = o;
_global.parseFloat = parseFloat;
_global.parseInt = parseInt;
_global.setInterval = setInterval;
_global.setTimeout = setTimeout;
_global.showRedrawRegions = showRedrawRegions;
_global.textRenderer = textRenderer;
_global.trace = trace;
_global.unescape = unescape;
_global.updateAfterEvent = updateAfterEvent;
#endif

function new_empty_object () {
  var hash = new Object ();
  ASSetPropFlags (hash, null, 0, 7);
  for (var prop in hash) {
    hash[prop] = "to-be-deleted";
    delete hash[prop];
  }
  return hash;
}

function hasOwnProperty_inner (o, prop)
{
  if (o.hasOwnProperty != undefined)
    return o.hasOwnProperty (prop);

  o.hasOwnProperty = ASnative (101, 5);
  var result = o.hasOwnProperty (prop);
  delete o.hasOwnProperty;
  return result;
}

function hasOwnProperty (o, prop)
{
  var result = hasOwnProperty_inner (o, prop);
#if __SWF_VERSION__ != 6
  if (result == false) {
    ASSetPropFlags (o, prop, 0, 256);
    result = hasOwnProperty_inner (o, prop);
    if (result)
      ASSetPropFlags (o, prop, 256);
  }
#endif
  return result;
}

function new_info () {
  return new_empty_object ();
}

function set_info (info, prop, id, value) {
  info[prop + "_-_" + id] = value;
}

function get_info (info, prop, id) {
  return info[prop + "_-_" + id];
}

function is_blaclisted (o, prop)
{
  if (prop == "mySecretId" || prop == "globalSecretId")
    return true;

  if (o == _global.Camera && prop == "names")
    return true;

  if (o == _global.Microphone && prop == "names")
    return true;

#if __SWF_VERSION__ < 6
  if (prop == "__proto__" && o[prop] == undefined)
    return true;
#endif

  return false;
}

function trace_properties_recurse (o, prefix, identifier, level)
{
  // to collect info about different properties
  var info = new_info ();

  // calculate indentation
  var indentation = "";
  for (var j = 0; j < level; j++) {
    indentation = indentation + "  ";
  }

  // mark the ones that are not hidden
  for (var prop in o)
  {
    // only get the ones that are not only in the __proto__
    if (is_blaclisted (o, prop) == false) {
      if (hasOwnProperty (o, prop) == true)
	set_info (info, prop, "hidden", false);
    }
  }

  // unhide everything
  ASSetPropFlags (o, null, 0, 1);

  var all = new Array ();
  var hidden = new Array ();
  for (var prop in o)
  {
    // only get the ones that are not only in the __proto__
    if (is_blaclisted (o, prop) == false) {
      if (hasOwnProperty (o, prop) == true) {
	all.push (prop);
	if (get_info (info, prop, "hidden") != false) {
	  set_info (info, prop, "hidden", true);
	  hidden.push (prop);
	}
      }
    }
  }
  all.sort ();

  // hide the ones that were already hidden
  ASSetPropFlags (o, hidden, 1, 0);

  if (all.length == 0) {
    trace (indentation + "no children");
    return nextSecretId;
  }

#if __SWF_VERSION__ != 6
  for (var i = 0; i < all.length; i++)
  {
    var prop = all[i];

    // try changing value
    if (!hasOwnProperty_inner(o, prop) && hasOwnProperty(o, prop))
    {
      set_info (info, prop, "not6", true);
    }
    else
    {
      set_info (info, prop, "not6", false);
    }
  }
#endif

  for (var i = 0; i < all.length; i++)
  {
    var prop = all[i];

    if (typeof (o[prop]) == "undefined") {
      ASSetPropFlags (o, prop, 0, 5248);
      if (typeof (o[prop]) != "undefined") {
	set_info (info, prop, "newer", true);
	// don't set the flags back
      } else {
	set_info (info, prop, "newer", false);
      }
    } else {
      set_info (info, prop, "newer", false);
    }
  }

  for (var i = 0; i < all.length; i++)
  {
    var prop = all[i];

    // try changing value
    var old = o[prop];
    var val = "hello " + o[prop];
    o[prop] = val;
    if (o[prop] != val)
    {
      set_info (info, prop, "constant", true);

      // try changing value after removing constant propflag
      ASSetPropFlags (o, prop, 0, 4);
      o[prop] = val;
      if (o[prop] != val) {
	set_info (info, prop, "superconstant", true);
      } else {
	set_info (info, prop, "superconstant", false);
	o[prop] = old;
      }
      ASSetPropFlags (o, prop, 4);
    }
    else
    {
      set_info (info, prop, "constant", false);
      set_info (info, prop, "superconstant", false);
      o[prop] = old;
    }
  }

  for (var i = 0; i < all.length; i++)
  {
    var prop = all[i];

    // remove constant flag
    ASSetPropFlags (o, prop, 0, 4);

    // try deleting
    var old = o[prop];
    delete o[prop];
    if (hasOwnProperty (o, prop))
    {
      set_info (info, prop, "permanent", true);
    }
    else
    {
      set_info (info, prop, "permanent", false);
      o[prop] = old;
    }

    // put constant flag back, if it was set
    if (get_info (info, prop, "constant") == true)
      ASSetPropFlags (o, prop, 4);
  }

  for (var i = 0; i < all.length; i++) {
    var prop = all[i];

    // format propflags
    var flags = "";
    if (get_info (info, prop, "hidden") == true) {
      flags += "h";
    }
    if (get_info (info, prop, "superpermanent") == true) {
      flags += "P";
    } else if (get_info (info, prop, "permanent") == true) {
      flags += "p";
    }
    if (get_info (info, prop, "superconstant") == true) {
      flags += "C";
    } else if (get_info (info, prop, "constant") == true) {
      flags += "c";
    }
    if (get_info (info, prop, "not6") == true) {
      flags += "6";
    }
    if (get_info (info, prop, "newer") == true) {
      flags += "n";
    }
    if (flags != "")
      flags = " (" + flags + ")";

    var value = "";
    // add value depending on the type
    if (typeof (o[prop]) == "number" || typeof (o[prop]) == "boolean") {
      value += " : " + o[prop];
    } else if (typeof (o[prop]) == "string") {
      value += " : \"" + o[prop] + "\"";
    }

    // recurse if it's object or function and this is the place it has been
    // named after
    if (typeof (o[prop]) == "object" || typeof (o[prop]) == "function")
    {
      if (prefix + (prefix != "" ? "." : "") + identifier + "." + prop ==
	  o[prop]["mySecretId"])
      {
	trace (indentation + prop + flags + " = " + typeof (o[prop]));
	trace_properties_recurse (o[prop], prefix + (prefix != "" ? "." : "") +
	    identifier, prop, level + 1);
      }
      else
      {
	trace (indentation + prop + flags + " = " + o[prop]["mySecretId"]);
      }
    }
    else
    {
      trace (indentation + prop + flags + " = " + typeof (o[prop]) + value);
    }
  }
}

function generate_names (o, prefix, identifier)
{
  // mark the ones that are not hidden
  var nothidden = new Array ();
  for (var prop in o) {
    nothidden.push (prop);
  }

  // unhide everything
  ASSetPropFlags (o, null, 0, 1);

  var all = new Array ();
  for (var prop in o)
  {
    if (is_blaclisted (o, prop) == false) {
      // only get the ones that are not only in the __proto__
      if (hasOwnProperty (o, prop) == true) {
	all.push (prop);
      }
    }
  }
  all.sort ();

  // hide the ones that were already hidden
  ASSetPropFlags (o, null, 1, 0);
  ASSetPropFlags (o, nothidden, 0, 1);

  for (var i = 0; i < all.length; i++) {
    var newer = false;
    var prop = all[i];

    if (typeof (o[prop]) == "undefined") {
      ASSetPropFlags (o, prop, 0, 5248);
      if (typeof (o[prop]) != "undefined")
	newer = true;
    }

    if (typeof (o[prop]) == "object" || typeof (o[prop]) == "function") {
      if (hasOwnProperty (o[prop], "mySecretId")) {
	all[i] = null; // don't recurse to it again
      } else {
	o[prop]["mySecretId"] = prefix + (prefix != "" ? "." : "") +
	  identifier + "." + prop;
      }
    }

    if (newer == true)
      ASSetPropFlags (o, prop, 5248);
  }

  for (var i = 0; i < all.length; i++) {
    var prop = all[i];

    if (prop != null) {
      var newer = false;

      if (typeof (o[prop]) == "undefined") {
	ASSetPropFlags (o, prop, 0, 5248);
	if (typeof (o[prop]) != "undefined")
	  newer = true;
      }

      if (typeof (o[prop]) == "object" || typeof (o[prop]) == "function")
	generate_names (o[prop], prefix + (prefix != "" ? "." : "") +
	    identifier, prop);

      if (newer == true)
	ASSetPropFlags (o, prop, 5248);
    }
  }
}

function trace_properties (o, prefix, identifier)
{
  _global["mySecretId"] = "_global";
  _global.Object["mySecretId"] = "_global.Object";
  _global.XMLNode["mySecretId"] = "_global.XMLNode";
  generate_names (_global.Object, "_global", "Object");
  generate_names (_global.XMLNode, "_global", "XMLNode");
  generate_names (_global, "", "_global");

  if (typeof (o) == "object" || typeof (o) == "function")
  {
    if (!o.hasOwnProperty ("mySecretId")) {
      o["mySecretId"] = prefix + (prefix != "" ? "." : "") + identifier;
      generate_names (o, prefix, identifier);
    }

    if (prefix + (prefix != "" ? "." : "") + identifier == o["mySecretId"])
    {
      trace (prefix + (prefix != "" ? "." : "") + identifier + " = " +
	  typeof (o));
    }
    else
    {
      trace (prefix + (prefix != "" ? "." : "") + identifier + " = " +
	  o["mySecretId"]);
    }
    trace_properties_recurse (o, prefix, identifier, 1);
  }
  else
  {
    var value = "";
    if (typeof (o) == "number" || typeof (o) == "boolean") {
      value += " : " + o;
    } else if (typeof (o) == "string") {
      value += " : \"" + o + "\"";
    }
    trace (prefix + (prefix != "" ? "." : "") + identifier + " = " +
	typeof (o) + value);
  }
}

#if __SWF_VERSION__ == 5
// create a _global object, since it doesn't have one, these are ver 6 values
_global = new_empty_object ();
_global.ASSetNative = ASSetNative;
_global.ASSetNativeAccessor = ASSetNativeAccessor;
_global.ASSetPropFlags = ASSetPropFlags;
_global.ASconstructor = ASconstructor;
_global.ASnative = ASnative;
_global.Accessibility = Accessibility;
_global.Array = Array;
_global.AsBroadcaster = AsBroadcaster;
_global.AsSetupError = AsSetupError;
_global.Boolean = Boolean;
_global.Button = Button;
_global.Camera = Camera;
_global.Color = Color;
_global.ContextMenu = ContextMenu;
_global.ContextMenuItem = ContextMenuItem;
_global.Date = Date;
_global.Error = Error;
_global.Function = Function;
_global.Infinity = Infinity;
_global.Key = Key;
_global.LoadVars = LoadVars;
_global.LocalConnection = LocalConnection;
_global.Math = Math;
_global.Microphone = Microphone;
_global.Mouse = Mouse;
_global.MovieClip = MovieClip;
_global.MovieClipLoader = MovieClipLoader;
_global.NaN = NaN;
_global.NetConnection = NetConnection;
_global.NetStream = NetStream;
_global.Number = Number;
_global.Object = Object;
_global.PrintJob = PrintJob;
_global.RemoteLSOUsage = RemoteLSOUsage;
_global.Selection = Selection;
_global.SharedObject = SharedObject;
_global.Sound = Sound;
_global.Stage = Stage;
_global.String = String;
_global.System = System;
_global.TextField = TextField;
_global.TextFormat = TextFormat;
_global.TextSnapshot = TextSnapshot;
_global.Video = Video;
_global.XML = XML;
_global.XMLNode = XMLNode;
_global.XMLSocket = XMLSocket;
_global.clearInterval = clearInterval;
_global.clearTimeout = clearTimeout;
_global.enableDebugConsole = enableDebugConsole;
_global.escape = escape;
_global.flash = flash;
_global.isFinite = isFinite;
_global.isNaN = isNaN;
_global.o = o;
_global.parseFloat = parseFloat;
_global.parseInt = parseInt;
_global.setInterval = setInterval;
_global.setTimeout = setTimeout;
_global.showRedrawRegions = showRedrawRegions;
_global.textRenderer = textRenderer;
_global.trace = trace;
_global.unescape = unescape;
_global.updateAfterEvent = updateAfterEvent;
#endif

function new_empty_object () {
  var hash = new Object ();
  ASSetPropFlags (hash, null, 0, 7);
  for (var prop in hash) {
    hash[prop] = "to-be-deleted";
    delete hash[prop];
  }
  return hash;
}

function hasOwnProperty_inner (o, prop)
{
  if (o.hasOwnProperty != undefined)
    return o.hasOwnProperty (prop);

  o.hasOwnProperty = ASnative (101, 5);
  var result = o.hasOwnProperty (prop);
  delete o.hasOwnProperty;
  return result;
}

function hasOwnProperty (o, prop)
{
  var result = hasOwnProperty_inner (o, prop);
#if __SWF_VERSION__ != 6
  if (result == false) {
    ASSetPropFlags (o, prop, 0, 256);
    result = hasOwnProperty_inner (o, prop);
    if (result)
      ASSetPropFlags (o, prop, 256);
  }
#endif
  return result;
}

function new_info () {
  return new_empty_object ();
}

function set_info (info, prop, id, value) {
  info[prop + "_-_" + id] = value;
}

function get_info (info, prop, id) {
  return info[prop + "_-_" + id];
}

function is_blaclisted (o, prop)
{
  if (prop == "mySecretId" || prop == "globalSecretId")
    return true;

  if (o == _global.Camera && prop == "names")
    return true;

  if (o == _global.Microphone && prop == "names")
    return true;

#if __SWF_VERSION__ < 6
  if (prop == "__proto__" && o[prop] == undefined)
    return true;
#endif

  return false;
}

function trace_properties_recurse (o, prefix, identifier, level)
{
  // to collect info about different properties
  var info = new_info ();

  // calculate indentation
  var indentation = "";
  for (var j = 0; j < level; j++) {
    indentation = indentation + "  ";
  }

  // mark the ones that are not hidden
  for (var prop in o)
  {
    // only get the ones that are not only in the __proto__
    if (is_blaclisted (o, prop) == false) {
      if (hasOwnProperty (o, prop) == true)
	set_info (info, prop, "hidden", false);
    }
  }

  // unhide everything
  ASSetPropFlags (o, null, 0, 1);

  var all = new Array ();
  var hidden = new Array ();
  for (var prop in o)
  {
    // only get the ones that are not only in the __proto__
    if (is_blaclisted (o, prop) == false) {
      if (hasOwnProperty (o, prop) == true) {
	all.push (prop);
	if (get_info (info, prop, "hidden") != false) {
	  set_info (info, prop, "hidden", true);
	  hidden.push (prop);
	}
      }
    }
  }
  all.sort ();

  // hide the ones that were already hidden
  ASSetPropFlags (o, hidden, 1, 0);

  if (all.length == 0) {
    trace (indentation + "no children");
    return nextSecretId;
  }

#if __SWF_VERSION__ != 6
  for (var i = 0; i < all.length; i++)
  {
    var prop = all[i];

    // try changing value
    if (!hasOwnProperty_inner(o, prop) && hasOwnProperty(o, prop))
    {
      set_info (info, prop, "not6", true);
    }
    else
    {
      set_info (info, prop, "not6", false);
    }
  }
#endif

  for (var i = 0; i < all.length; i++)
  {
    var prop = all[i];

    if (typeof (o[prop]) == "undefined") {
      ASSetPropFlags (o, prop, 0, 5248);
      if (typeof (o[prop]) != "undefined") {
	set_info (info, prop, "newer", true);
	// don't set the flags back
      } else {
	set_info (info, prop, "newer", false);
      }
    } else {
      set_info (info, prop, "newer", false);
    }
  }

  for (var i = 0; i < all.length; i++)
  {
    var prop = all[i];

    // try changing value
    var old = o[prop];
    var val = "hello " + o[prop];
    o[prop] = val;
    if (o[prop] != val)
    {
      set_info (info, prop, "constant", true);

      // try changing value after removing constant propflag
      ASSetPropFlags (o, prop, 0, 4);
      o[prop] = val;
      if (o[prop] != val) {
	set_info (info, prop, "superconstant", true);
      } else {
	set_info (info, prop, "superconstant", false);
	o[prop] = old;
      }
      ASSetPropFlags (o, prop, 4);
    }
    else
    {
      set_info (info, prop, "constant", false);
      set_info (info, prop, "superconstant", false);
      o[prop] = old;
    }
  }

  for (var i = 0; i < all.length; i++)
  {
    var prop = all[i];

    // remove constant flag
    ASSetPropFlags (o, prop, 0, 4);

    // try deleting
    var old = o[prop];
    delete o[prop];
    if (hasOwnProperty (o, prop))
    {
      set_info (info, prop, "permanent", true);
    }
    else
    {
      set_info (info, prop, "permanent", false);
      o[prop] = old;
    }

    // put constant flag back, if it was set
    if (get_info (info, prop, "constant") == true)
      ASSetPropFlags (o, prop, 4);
  }

  for (var i = 0; i < all.length; i++) {
    var prop = all[i];

    // format propflags
    var flags = "";
    if (get_info (info, prop, "hidden") == true) {
      flags += "h";
    }
    if (get_info (info, prop, "superpermanent") == true) {
      flags += "P";
    } else if (get_info (info, prop, "permanent") == true) {
      flags += "p";
    }
    if (get_info (info, prop, "superconstant") == true) {
      flags += "C";
    } else if (get_info (info, prop, "constant") == true) {
      flags += "c";
    }
    if (get_info (info, prop, "not6") == true) {
      flags += "6";
    }
    if (get_info (info, prop, "newer") == true) {
      flags += "n";
    }
    if (flags != "")
      flags = " (" + flags + ")";

    var value = "";
    // add value depending on the type
    if (typeof (o[prop]) == "number" || typeof (o[prop]) == "boolean") {
      value += " : " + o[prop];
    } else if (typeof (o[prop]) == "string") {
      value += " : \"" + o[prop] + "\"";
    }

    // recurse if it's object or function and this is the place it has been
    // named after
    if (typeof (o[prop]) == "object" || typeof (o[prop]) == "function")
    {
      if (prefix + (prefix != "" ? "." : "") + identifier + "." + prop ==
	  o[prop]["mySecretId"])
      {
	trace (indentation + prop + flags + " = " + typeof (o[prop]));
	trace_properties_recurse (o[prop], prefix + (prefix != "" ? "." : "") +
	    identifier, prop, level + 1);
      }
      else
      {
	trace (indentation + prop + flags + " = " + o[prop]["mySecretId"]);
      }
    }
    else
    {
      trace (indentation + prop + flags + " = " + typeof (o[prop]) + value);
    }
  }
}

function generate_names (o, prefix, identifier)
{
  // mark the ones that are not hidden
  var nothidden = new Array ();
  for (var prop in o) {
    nothidden.push (prop);
  }

  // unhide everything
  ASSetPropFlags (o, null, 0, 1);

  var all = new Array ();
  for (var prop in o)
  {
    if (is_blaclisted (o, prop) == false) {
      // only get the ones that are not only in the __proto__
      if (hasOwnProperty (o, prop) == true) {
	all.push (prop);
      }
    }
  }
  all.sort ();

  // hide the ones that were already hidden
  ASSetPropFlags (o, null, 1, 0);
  ASSetPropFlags (o, nothidden, 0, 1);

  for (var i = 0; i < all.length; i++) {
    var newer = false;
    var prop = all[i];

    if (typeof (o[prop]) == "undefined") {
      ASSetPropFlags (o, prop, 0, 5248);
      if (typeof (o[prop]) != "undefined")
	newer = true;
    }

    if (typeof (o[prop]) == "object" || typeof (o[prop]) == "function") {
      if (hasOwnProperty (o[prop], "mySecretId")) {
	all[i] = null; // don't recurse to it again
      } else {
	o[prop]["mySecretId"] = prefix + (prefix != "" ? "." : "") +
	  identifier + "." + prop;
      }
    }

    if (newer == true)
      ASSetPropFlags (o, prop, 5248);
  }

  for (var i = 0; i < all.length; i++) {
    var prop = all[i];

    if (prop != null) {
      var newer = false;

      if (typeof (o[prop]) == "undefined") {
	ASSetPropFlags (o, prop, 0, 5248);
	if (typeof (o[prop]) != "undefined")
	  newer = true;
      }

      if (typeof (o[prop]) == "object" || typeof (o[prop]) == "function")
	generate_names (o[prop], prefix + (prefix != "" ? "." : "") +
	    identifier, prop);

      if (newer == true)
	ASSetPropFlags (o, prop, 5248);
    }
  }
}

function trace_properties (o, prefix, identifier)
{
  _global["mySecretId"] = "_global";
  _global.Object["mySecretId"] = "_global.Object";
  _global.Function["mySecretId"] = "_global.Function";
  _global.Function.prototype["mySecretId"] = "_global.Function.prototype";
  _global.XMLNode["mySecretId"] = "_global.XMLNode";
  generate_names (_global.Object, "_global", "Object");
  generate_names (_global.Function, "_global", "Function");
  generate_names (_global.Function.prototype, "_global", "Function.prototype");
  generate_names (_global.XMLNode, "_global", "XMLNode");
  generate_names (_global, "", "_global");

  if (typeof (o) == "object" || typeof (o) == "function")
  {
    if (!o.hasOwnProperty ("mySecretId")) {
      o["mySecretId"] = prefix + (prefix != "" ? "." : "") + identifier;
      generate_names (o, prefix, identifier);
    }

    if (prefix + (prefix != "" ? "." : "") + identifier == o["mySecretId"])
    {
      trace (prefix + (prefix != "" ? "." : "") + identifier + " = " +
	  typeof (o));
    }
    else
    {
      trace (prefix + (prefix != "" ? "." : "") + identifier + " = " +
	  o["mySecretId"]);
    }
    trace_properties_recurse (o, prefix, identifier, 1);
  }
  else
  {
    var value = "";
    if (typeof (o) == "number" || typeof (o) == "boolean") {
      value += " : " + o;
    } else if (typeof (o) == "string") {
      value += " : \"" + o + "\"";
    }
    trace (prefix + (prefix != "" ? "." : "") + identifier + " = " +
	typeof (o) + value);
  }
}

Fix trace_properties.as to name Function and Function.prototype properly


package aerys.minko.render.shader.part.phong.attenuation
{
	import aerys.minko.render.material.phong.PhongProperties;
	import aerys.minko.render.shader.SFloat;
	import aerys.minko.render.shader.Shader;
	import aerys.minko.render.shader.part.phong.LightAwareShaderPart;
	import aerys.minko.type.enum.SamplerFiltering;
	import aerys.minko.type.enum.SamplerMipMapping;
	import aerys.minko.type.enum.SamplerWrapping;
	import aerys.minko.type.enum.ShadowMappingQuality;

	/**
	 * Fixme, bias should be:Total bias is m*SLOPESCALE + DEPTHBIAS
	 * Where m = max( | ∂z/∂x | , | ∂z/∂y | )
	 * ftp://download.nvidia.com/developer/presentations/2004/GPU_Jackpot/Shadow_Mapping.pdf
	 *
	 * @author Romain Gilliotte
	 */
	public class MatrixShadowMapAttenuationShaderPart extends LightAwareShaderPart implements IAttenuationShaderPart
	{
		private static const DEFAULT_BIAS : Number = 1 / 256 / 256;

		public function MatrixShadowMapAttenuationShaderPart(main : Shader)
		{
			super(main);
		}

		public function getAttenuation(lightId : uint) : SFloat
		{
			var lightType	: uint		= getLightConstant(lightId, 'type');
			var screenPos	: SFloat	= interpolate(localToScreen(fsLocalPosition));

			// retrieve shadow bias
			var shadowBias : SFloat;
			if (meshBindings.propertyExists(PhongProperties.SHADOW_BIAS))
				shadowBias = meshBindings.getParameter(PhongProperties.SHADOW_BIAS, 1);
			else if (sceneBindings.propertyExists(PhongProperties.SHADOW_BIAS))
				shadowBias = sceneBindings.getParameter(PhongProperties.SHADOW_BIAS, 1);
			else
				shadowBias = float(DEFAULT_BIAS);

			// retrieve depthmap and projection matrix
			var worldToUV	: SFloat = getLightParameter(lightId, 'worldToUV', 16);
			var depthMap	: SFloat = getLightTextureParameter(
				lightId,
				'shadowMap',
				SamplerFiltering.NEAREST,
				SamplerMipMapping.DISABLE,
				SamplerWrapping.CLAMP
			);

			// read expected depth from shadow map, and compute current depth
			var uv : SFloat;
			uv = multiply4x4(vsWorldPosition, worldToUV);
			uv = interpolate(uv);

			var currentDepth : SFloat = uv.z;
			currentDepth = min(subtract(1, shadowBias), currentDepth);

			uv = divide(uv, uv.w);

			var outsideMap			: SFloat = notEqual(
                0,
                dotProduct4(notEqual(uv, saturate(uv)), notEqual(uv, saturate(uv)))
            );

			var precomputedDepth	: SFloat = unpack(sampleTexture(depthMap, uv.xyyy));

			var curDepthSubBias		: SFloat = subtract(currentDepth, shadowBias);
			var noShadows			: SFloat = lessEqual(curDepthSubBias, precomputedDepth);

			var quality		: uint		= getLightConstant(lightId, 'shadowQuality');

			if (quality != ShadowMappingQuality.HARD)
			{
				var invertSize	: SFloat			= divide(
					getLightParameter(lightId, 'shadowSpread', 1),
					getLightParameter(lightId, 'shadowMapSize', 1)
				);

				var uvs 		: Vector.<SFloat>	= new <SFloat>[];
				var uvDelta		: SFloat;

				if (quality > ShadowMappingQuality.LOW)
				{
					uvDelta = multiply(float3(-1, 0, 1), invertSize);
					uvs.push(
						add(uv.xyxy, uvDelta.xxxy),	// (-1, -1), (-1,  0)
						add(uv.xyxy, uvDelta.xzyx),	// (-1,  1), ( 0, -1)
						add(uv.xyxy, uvDelta.yzzx),	// ( 0,  1), ( 1, -1)
						add(uv.xyxy, uvDelta.zyzz)	// ( 1,  0), ( 1,  1)
					);
				}

				if (quality > ShadowMappingQuality.MEDIUM)
				{
					uvDelta = multiply(float3(-2, 0, 2), invertSize);
					uvs.push(
						add(uv.xyxy, uvDelta.xyyx),	// (-2, 0), (0, -2)
						add(uv.xyxy, uvDelta.yzzy)	// ( 0, 2), (2, 0)
					);
				}

				if (quality > ShadowMappingQuality.HARD)
				{
					uvDelta = multiply(float4(-2, -1, 1, 2), invertSize);
					uvs.push(
						add(uv.xyxy, uvDelta.xzyw),	// (-2,  1), (-1,  2)
						add(uv.xyxy, uvDelta.zwwz),	// ( 1,  2), ( 2,  1)
						add(uv.xyxy, uvDelta.wyzx),	// ( 2, -1), ( 1, -2)
						add(uv.xyxy, uvDelta.xyyx)	// (-2, -1), (-1, -2)
					);
				}

				var numSamples : uint = uvs.length;
				for (var sampleId : uint = 0; sampleId < numSamples; sampleId += 2)
				{
					precomputedDepth = float4(
						unpack(sampleTexture(depthMap, uvs[sampleId].xy)),
						unpack(sampleTexture(depthMap, uvs[sampleId].zw)),
						unpack(sampleTexture(depthMap, uvs[sampleId + 1].xy)),
						unpack(sampleTexture(depthMap, uvs[sampleId + 1].zw))
					);

					var localNoShadows : SFloat = lessEqual(curDepthSubBias, precomputedDepth);
					noShadows.incrementBy(dotProduct4(localNoShadows, float4(1, 1, 1, 1)));
				}

				noShadows.scaleBy(1 / (2 * numSamples + 1));
			}

			return noShadows;
		}
	}
}

package aerys.minko.render.shader.part.phong.attenuation
{
	import aerys.minko.render.material.phong.PhongProperties;
	import aerys.minko.render.shader.SFloat;
	import aerys.minko.render.shader.Shader;
	import aerys.minko.render.shader.part.phong.LightAwareShaderPart;
	import aerys.minko.type.enum.SamplerFiltering;
	import aerys.minko.type.enum.SamplerMipMapping;
	import aerys.minko.type.enum.SamplerWrapping;
	import aerys.minko.type.enum.ShadowMappingQuality;

	/**
	 * Fixme, bias should be:Total bias is m*SLOPESCALE + DEPTHBIAS
	 * Where m = max( | ∂z/∂x | , | ∂z/∂y | )
	 * ftp://download.nvidia.com/developer/presentations/2004/GPU_Jackpot/Shadow_Mapping.pdf
	 *
	 * @author Romain Gilliotte
	 */
	public class MatrixShadowMapAttenuationShaderPart extends LightAwareShaderPart implements IAttenuationShaderPart
	{
		public function MatrixShadowMapAttenuationShaderPart(main : Shader)
		{
			super(main);
		}

		public function getAttenuation(lightId : uint) : SFloat
		{
			var lightType	: uint		= getLightConstant(lightId, 'type');
			var screenPos	: SFloat	= interpolate(localToScreen(fsLocalPosition));

			// retrieve shadow bias
			var shadowBias : SFloat;
			if (meshBindings.propertyExists(PhongProperties.SHADOW_BIAS))
				shadowBias = meshBindings.getParameter(PhongProperties.SHADOW_BIAS, 1);
			else
				shadowBias = getLightParameter(lightId, PhongProperties.SHADOW_BIAS, 1);

			// retrieve depthmap and projection matrix
			var worldToUV	: SFloat = getLightParameter(lightId, 'worldToUV', 16);
			var depthMap	: SFloat = getLightTextureParameter(
				lightId,
				'shadowMap',
				SamplerFiltering.NEAREST,
				SamplerMipMapping.DISABLE,
				SamplerWrapping.CLAMP
			);

			// read expected depth from shadow map, and compute current depth
			var uv : SFloat;
			uv = multiply4x4(vsWorldPosition, worldToUV);
			uv = interpolate(uv);

			var currentDepth : SFloat = uv.z;
			currentDepth = min(subtract(1, shadowBias), currentDepth);

			uv = divide(uv, uv.w);

			var outsideMap			: SFloat = notEqual(
                0,
                dotProduct4(notEqual(uv, saturate(uv)), notEqual(uv, saturate(uv)))
            );

			var precomputedDepth	: SFloat = unpack(sampleTexture(depthMap, uv.xyyy));

			var curDepthSubBias		: SFloat = subtract(currentDepth, shadowBias);
			var noShadows			: SFloat = lessEqual(curDepthSubBias, precomputedDepth);

			var quality		: uint		= getLightConstant(lightId, 'shadowQuality');

			if (quality != ShadowMappingQuality.HARD)
			{
				var invertSize	: SFloat			= divide(
					getLightParameter(lightId, 'shadowSpread', 1),
					getLightParameter(lightId, 'shadowMapSize', 1)
				);

				var uvs 		: Vector.<SFloat>	= new <SFloat>[];
				var uvDelta		: SFloat;

				if (quality > ShadowMappingQuality.LOW)
				{
					uvDelta = multiply(float3(-1, 0, 1), invertSize);
					uvs.push(
						add(uv.xyxy, uvDelta.xxxy),	// (-1, -1), (-1,  0)
						add(uv.xyxy, uvDelta.xzyx),	// (-1,  1), ( 0, -1)
						add(uv.xyxy, uvDelta.yzzx),	// ( 0,  1), ( 1, -1)
						add(uv.xyxy, uvDelta.zyzz)	// ( 1,  0), ( 1,  1)
					);
				}

				if (quality > ShadowMappingQuality.MEDIUM)
				{
					uvDelta = multiply(float3(-2, 0, 2), invertSize);
					uvs.push(
						add(uv.xyxy, uvDelta.xyyx),	// (-2, 0), (0, -2)
						add(uv.xyxy, uvDelta.yzzy)	// ( 0, 2), (2, 0)
					);
				}

				if (quality > ShadowMappingQuality.HARD)
				{
					uvDelta = multiply(float4(-2, -1, 1, 2), invertSize);
					uvs.push(
						add(uv.xyxy, uvDelta.xzyw),	// (-2,  1), (-1,  2)
						add(uv.xyxy, uvDelta.zwwz),	// ( 1,  2), ( 2,  1)
						add(uv.xyxy, uvDelta.wyzx),	// ( 2, -1), ( 1, -2)
						add(uv.xyxy, uvDelta.xyyx)	// (-2, -1), (-1, -2)
					);
				}

				var numSamples : uint = uvs.length;
				for (var sampleId : uint = 0; sampleId < numSamples; sampleId += 2)
				{
					precomputedDepth = float4(
						unpack(sampleTexture(depthMap, uvs[sampleId].xy)),
						unpack(sampleTexture(depthMap, uvs[sampleId].zw)),
						unpack(sampleTexture(depthMap, uvs[sampleId + 1].xy)),
						unpack(sampleTexture(depthMap, uvs[sampleId + 1].zw))
					);

					var localNoShadows : SFloat = lessEqual(curDepthSubBias, precomputedDepth);
					noShadows.incrementBy(dotProduct4(localNoShadows, float4(1, 1, 1, 1)));
				}

				noShadows.scaleBy(1 / (2 * numSamples + 1));
			}

			return noShadows;
		}
	}
}

read the shadow bias from the light data provider


//
// Flump - Copyright 2013 Flump Authors
package flump.mold {

/** @private */
public class KeyframeMold
{
    public var index :int;

    /** The length of this keyframe in frames. */
    public var duration :int;

    /**
     * The symbol of the image or movie in this keyframe, or null if there is nothing in it.
     * For flipbook frames, this will be a name constructed out of the movie and frame index.
     */
    public var ref :String;

    /** The label on this keyframe, or null if there isn't one */
    public var label :String;

    /** Exploded values from matrix */
    public var x :Number = 0.0, y :Number = 0.0, scaleX :Number = 1.0, scaleY :Number = 1.0,
        skewX :Number = 0.0, skewY :Number = 0.0;

    /** Transformation point */
    public var pivotX :Number = 0.0, pivotY :Number = 0.0;

    public var alpha :Number = 1;

    public var visible :Boolean = true;

    /** Is this keyframe tweened? */
    public var tweened :Boolean = true;

    /** Tween easing. Only valid if tweened==true. */
    public var ease :Number = 0;

    public static function fromJSON (o :Object) :KeyframeMold {
        const mold :KeyframeMold = new KeyframeMold();
        mold.index = require(o, "index");
        mold.duration = require(o, "duration");
        extractField(o, mold, "ref");
        extractFields(o, mold, "loc", "x", "y");
        extractFields(o, mold, "scale", "scaleX", "scaleY");
        extractFields(o, mold, "skew", "skewX", "skewY");
        extractFields(o, mold, "pivot", "pivotX", "pivotY");
        extractField(o, mold, "alpha");
        extractField(o, mold, "visible");
        extractField(o, mold, "ease");
        extractField(o, mold, "tweened");
        extractField(o, mold, "label");
        return mold;
    }

    /** True if this keyframe does not display anything. */
    public function get isEmpty () :Boolean { return this.ref == null; }

    public function get rotation () :Number { return skewX; }
    // public function set rotation (angle :Number) :void { skewX = skewY = angle; }

    public function rotate (delta :Number) :void {
        skewX += delta;
        skewY += delta;
    }

    public function toJSON (_:*) :Object {
        var json :Object = {
            index: index,
            duration: duration
        };
        if (ref != null) {
            json.ref = ref;
            if (x != 0 || y != 0) json.loc = [round(x), round(y)];
            if (scaleX != 1 || scaleY != 1) json.scale = [round(scaleX), round(scaleY)];
            if (skewX != 0 || skewY != 0) json.skew = [round(skewX), round(skewY)];
            if (pivotX != 0 || pivotY != 0) json.pivot = [round(pivotX), round(pivotY)];
            if (alpha != 1) json.alpha = round(alpha);
            if (!visible) json.visible = visible;
            if (!tweened) json.tweened = tweened;
            if (ease != 0) json.ease = round(ease);
        }
        if (label != null) json.label = label;
        return json;
    }

    public function toXML () :XML {
        var xml :XML = <kf duration={duration}/>;
        if (ref != null) {
            xml.@ref = ref;
            if (x != 0 || y != 0) xml.@loc = "" + round(x) + "," + round(y);
            if (scaleX != 1 || scaleY != 1) xml.@scale = "" + round(scaleX) + "," + round(scaleY);
            if (skewX != 0 || skewY != 0) xml.@skew = "" + round(skewX) + "," + round(skewY);
            if (pivotX != 0 || pivotY != 0) xml.@pivot = "" + round(pivotX) + "," + round(pivotY);
            if (alpha != 1) xml.@alpha = round(alpha);
            if (!visible) xml.@visible = visible;
            if (!tweened) xml.@tweened = tweened;
            if (ease != 0) xml.@ease = round(ease);
        }
        if (label != null) xml.@label = label;
        return xml;
    }

    protected static function extractFields(o :Object, destObj :Object, source :String,
        dest1 :String, dest2 :String) :void {
        const extracted :* = o[source];
        if (extracted === undefined) return;
        destObj[dest1] = extracted[0];
        destObj[dest2] = extracted[1];
    }

    protected static function extractField(o :Object, destObj :Object, field :String) :void {
        const extracted :* = o[field];
        if (extracted === undefined) return;
        destObj[field] = extracted;
    }

    protected static function round (n :Number, places :int = 4) :Number {
        const shift :int = Math.pow(10, places);
        return Math.round(n * shift) / shift;
    }

}
}

//
// Flump - Copyright 2013 Flump Authors
package flump.mold {

/** @private */
public class KeyframeMold
{
    /**
     * The index of the first frame in the keyframe.
     * (Equivalent to prevKeyframe.index + prevKeyframe.duration)
     */
    public var index :int;

    /** The length of this keyframe in frames. */
    public var duration :int;

    /**
     * The symbol of the image or movie in this keyframe, or null if there is nothing in it.
     * For flipbook frames, this will be a name constructed out of the movie and frame index.
     */
    public var ref :String;

    /** The label on this keyframe, or null if there isn't one */
    public var label :String;

    /** Exploded values from matrix */
    public var x :Number = 0.0, y :Number = 0.0, scaleX :Number = 1.0, scaleY :Number = 1.0,
        skewX :Number = 0.0, skewY :Number = 0.0;

    /** Transformation point */
    public var pivotX :Number = 0.0, pivotY :Number = 0.0;

    public var alpha :Number = 1;

    public var visible :Boolean = true;

    /** Is this keyframe tweened? */
    public var tweened :Boolean = true;

    /** Tween easing. Only valid if tweened==true. */
    public var ease :Number = 0;

    public static function fromJSON (o :Object) :KeyframeMold {
        const mold :KeyframeMold = new KeyframeMold();
        mold.index = require(o, "index");
        mold.duration = require(o, "duration");
        extractField(o, mold, "ref");
        extractFields(o, mold, "loc", "x", "y");
        extractFields(o, mold, "scale", "scaleX", "scaleY");
        extractFields(o, mold, "skew", "skewX", "skewY");
        extractFields(o, mold, "pivot", "pivotX", "pivotY");
        extractField(o, mold, "alpha");
        extractField(o, mold, "visible");
        extractField(o, mold, "ease");
        extractField(o, mold, "tweened");
        extractField(o, mold, "label");
        return mold;
    }

    /** True if this keyframe does not display anything. */
    public function get isEmpty () :Boolean { return this.ref == null; }

    public function get rotation () :Number { return skewX; }
    // public function set rotation (angle :Number) :void { skewX = skewY = angle; }

    public function rotate (delta :Number) :void {
        skewX += delta;
        skewY += delta;
    }

    public function toJSON (_:*) :Object {
        var json :Object = {
            index: index,
            duration: duration
        };
        if (ref != null) {
            json.ref = ref;
            if (x != 0 || y != 0) json.loc = [round(x), round(y)];
            if (scaleX != 1 || scaleY != 1) json.scale = [round(scaleX), round(scaleY)];
            if (skewX != 0 || skewY != 0) json.skew = [round(skewX), round(skewY)];
            if (pivotX != 0 || pivotY != 0) json.pivot = [round(pivotX), round(pivotY)];
            if (alpha != 1) json.alpha = round(alpha);
            if (!visible) json.visible = visible;
            if (!tweened) json.tweened = tweened;
            if (ease != 0) json.ease = round(ease);
        }
        if (label != null) json.label = label;
        return json;
    }

    public function toXML () :XML {
        var xml :XML = <kf duration={duration}/>;
        if (ref != null) {
            xml.@ref = ref;
            if (x != 0 || y != 0) xml.@loc = "" + round(x) + "," + round(y);
            if (scaleX != 1 || scaleY != 1) xml.@scale = "" + round(scaleX) + "," + round(scaleY);
            if (skewX != 0 || skewY != 0) xml.@skew = "" + round(skewX) + "," + round(skewY);
            if (pivotX != 0 || pivotY != 0) xml.@pivot = "" + round(pivotX) + "," + round(pivotY);
            if (alpha != 1) xml.@alpha = round(alpha);
            if (!visible) xml.@visible = visible;
            if (!tweened) xml.@tweened = tweened;
            if (ease != 0) xml.@ease = round(ease);
        }
        if (label != null) xml.@label = label;
        return xml;
    }

    protected static function extractFields(o :Object, destObj :Object, source :String,
        dest1 :String, dest2 :String) :void {
        const extracted :* = o[source];
        if (extracted === undefined) return;
        destObj[dest1] = extracted[0];
        destObj[dest2] = extracted[1];
    }

    protected static function extractField(o :Object, destObj :Object, field :String) :void {
        const extracted :* = o[field];
        if (extracted === undefined) return;
        destObj[field] = extracted;
    }

    protected static function round (n :Number, places :int = 4) :Number {
        const shift :int = Math.pow(10, places);
        return Math.round(n * shift) / shift;
    }

}
}

Add documentation for KeyframeMold.index