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

package com.threerings.util {

/**
 * Guarantees a specific iteration order for keys(), values(), and forEach() with as little
 * additional overhead as possible.
 */
public class SortedHashMap extends HashMap
{
    public static const COMPARABLE_KEYS :int = 0;
    public static const STRING_KEYS :int = 1;
    public static const NUMERIC_KEYS :int = 2;

    public function SortedHashMap (
        keyType :int,
        loadFactor :Number = 1.75, equalsFn :Function = null, hashFn :Function = null)
    {
        super(loadFactor, equalsFn, hashFn);
        _keyType = keyType;
    }

    override public function put (key :Object, value :Object) :*
    {
        validateKey(key);
        return super.put(key, value);
    }

    override public function keys () :Array
    {
        var keys :Array = super.keys();

        switch (_keyType) {
        case COMPARABLE_KEYS:
            ArrayUtil.sort(keys);
            break;

        case STRING_KEYS:
        default:
            keys.sort();
            break;

        case NUMERIC_KEYS:
            keys.sort(Array.NUMERIC);
            break;
        }

        return keys;
    }

    override public function values () :Array
    {
        // not very optimal, we need to look up each value from the sorted keys.
        return keys().map(function (key :Object, ... rest) :Object {
            return get(key);
        });
    }

    override public function forEach (fn :Function) :void
    {
        var keys :Array = keys();
        for each (var key :Object in keys) {
            fn(key, get(key));
        }
    }

    protected function validateKey (key :Object) :void
        // throws ArgumentError
    {
        if (key == null) {
            return;
        }
        switch (_keyType) {
        case COMPARABLE_KEYS:
            if (key is Comparable) {
                return;
            }
            break;

        case STRING_KEYS:
            if (key is String) {
                return;
            }
            break;

        case NUMERIC_KEYS:
            if (key is Number) {
                return;
            }
            break;

        default:
            // there is no return
            break;
        }

        throw new ArgumentError("Invalid key");
    }

    /** The key type in use for this map. */
    protected var _keyType :int;
}
}

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

package com.threerings.util {

/**
 * Guarantees a specific iteration order for keys(), values(), and forEach() with as little
 * additional overhead as possible.
 */
public class SortedHashMap extends HashMap
{
    public static const COMPARABLE_KEYS :int = 0;
    public static const STRING_KEYS :int = 1;
    public static const NUMERIC_KEYS :int = 2;

    public function SortedHashMap (
        keyType :int,
        loadFactor :Number = 1.75, equalsFn :Function = null, hashFn :Function = null)
    {
        super(loadFactor, equalsFn, hashFn);
        _keyType = keyType;
    }

    override public function put (key :Object, value :Object) :*
    {
        validateKey(key);
        return super.put(key, value);
    }

    override public function keys () :Array
    {
        var keys :Array = super.keys();

        switch (_keyType) {
        case COMPARABLE_KEYS:
            ArrayUtil.sort(keys);
            break;

        case STRING_KEYS:
        default:
            keys.sort();
            break;

        case NUMERIC_KEYS:
            keys.sort(Array.NUMERIC);
            break;
        }

        return keys;
    }

    override public function values () :Array
    {
        // not very optimal, we need to look up each value from the sorted keys.
        return keys().map(Util.adapt(get));
    }

    override public function forEach (fn :Function) :void
    {
        var keys :Array = keys();
        for each (var key :Object in keys) {
            fn(key, get(key));
        }
    }

    protected function validateKey (key :Object) :void
        // throws ArgumentError
    {
        if (key == null) {
            return;
        }
        switch (_keyType) {
        case COMPARABLE_KEYS:
            if (key is Comparable) {
                return;
            }
            break;

        case STRING_KEYS:
            if (key is String) {
                return;
            }
            break;

        case NUMERIC_KEYS:
            if (key is Number) {
                return;
            }
            break;

        default:
            // there is no return
            break;
        }

        throw new ArgumentError("Invalid key");
    }

    /** The key type in use for this map. */
    protected var _keyType :int;
}
}

Use Util.adapt(). Look at this functional programming masterpiece.
I'm not sure how much actual utility is here, and how much I just like the
gee-whiz factor.

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


//
// Flump - Copyright 2013 Flump Authors

package flump.xfl {

import flash.filesystem.File;
import flash.utils.ByteArray;
import flash.utils.Dictionary;

import com.adobe.crypto.MD5;

import flump.SwfTexture;
import flump.Util;
import flump.executor.Executor;
import flump.executor.Future;
import flump.executor.FutureTask;
import flump.executor.load.LoadedSwf;
import flump.executor.load.SwfLoader;
import flump.export.Atlas;
import flump.export.ExportConf;
import flump.export.Files;
import flump.mold.KeyframeMold;
import flump.mold.LayerMold;
import flump.mold.LibraryMold;
import flump.mold.MovieMold;
import flump.mold.TextureGroupMold;

import com.threerings.util.Log;
import com.threerings.util.Map;
import com.threerings.util.Maps;
import com.threerings.util.Set;
import com.threerings.util.Sets;
import com.threerings.util.XmlUtil;

public class XflLibrary
{
    use namespace xflns;

    /**
     * When an exported movie contains an unexported movie, it gets assigned a generated symbol
     * name with this prefix.
     */
    public static const IMPLICIT_PREFIX :String = "~";

    public var swf :LoadedSwf;

    public var frameRate :Number;
    public var backgroundColor :int;

    // The MD5 of the published library SWF
    public var md5 :String;

    public var location :String;

    public const movies :Vector.<MovieMold> = new <MovieMold>[];
    public const textures :Vector.<XflTexture> = new <XflTexture>[];

    public function XflLibrary (location :String) {
        this.location = location;
    }

    public function getItem (id :String, requiredType :Class=null) :* {
        const result :* = _idToItem[id];
        if (result === undefined) throw new Error("Unknown library item '" + id + "'");
        else if (requiredType != null) return requiredType(result);
        else return result;
    }

    public function isExported (movie :MovieMold) :Boolean {
        return _moldToSymbol.containsKey(movie);
    }

    public function get publishedMovies () :Vector.<MovieMold> {
        const result :Vector.<MovieMold> = new <MovieMold>[];
        for each (var movie :MovieMold in _toPublish.toArray().sortOn("id")) result.push(movie);
        return result;
    }

    public function finishLoading () :void {
        var movie :MovieMold = null;

        // Parse all un-exported movies that are referenced by the exported movies.
        for (var ii :int = 0; ii < movies.length; ++ii) {
            movie = movies[ii];
            for each (var symbolName :String in XflMovie.getSymbolNames(movie).toArray()) {
                var xml :XML = _unexportedMovies.remove(symbolName);
                if (xml != null) parseMovie(xml);
            }
        }

        for each (movie in movies) if (isExported(movie)) prepareForPublishing(movie);
    }

    protected function prepareForPublishing (movie :MovieMold) :void {
        if (!_toPublish.add(movie) || movie.flipbook) return;
        for each (var layer :LayerMold in movie.layers) {
            for each (var kf :KeyframeMold in layer.keyframes) {
                if (kf.ref == null) continue;
                kf.ref = _libraryNameToId.get(kf.ref);
                var item :Object = _idToItem[kf.ref];
                if (item == null) {
                    addTopLevelError(ParseError.CRIT,
                            "unrecognized library item '" + kf.ref + "'");
                } else if (item is MovieMold) {
                    prepareForPublishing(MovieMold(item));
                } else if (item is XflTexture) {
                    // Texture symbols have origins. For texture layer keyframes,
                    // we combine the texture's origin with the keyframe's pivot point.
                    var tex :SwfTexture = SwfTexture.fromTexture(this.swf, XflTexture(item));
                    kf.pivotX += tex.origin.x;
                    kf.pivotY += tex.origin.y;
                }
            }
        }
    }

    public function createId (item :Object, libraryName :String, symbol :String) :String {
        if (symbol != null) _moldToSymbol.put(item, symbol);
        const id :String = symbol == null ? IMPLICIT_PREFIX + libraryName : symbol;
        _libraryNameToId.put(libraryName, id);
        _idToItem[id] = item;
        return id;
    }

    public function getErrors (sev :String=null) :Vector.<ParseError> {
        if (sev == null) return _errors;
        const sevOrdinal :int = ParseError.severityToOrdinal(sev);
        return _errors.filter(function (err :ParseError, ..._) :Boolean {
            return err.sevOrdinal >= sevOrdinal;
        });
    }

    public function get valid () :Boolean { return getErrors(ParseError.CRIT).length == 0; }

    public function addTopLevelError (severity :String, message :String, e :Object=null) :void {
        addError(location, severity, message, e);
    }

    public function addError (location :String, severity :String, message :String, e :Object=null) :void {
        _errors.push(new ParseError(location, severity, message, e));
    }

    public function toJSONString (atlases :Vector.<Atlas>, conf :ExportConf, pretty :Boolean=false) :String {
        return JSON.stringify(toMold(atlases, conf), null, pretty ? "  " : null);
    }

    public function toMold (atlases :Vector.<Atlas>, conf :ExportConf) :LibraryMold {
        const mold :LibraryMold = new LibraryMold();
        mold.frameRate = frameRate;
        mold.md5 = md5;
        mold.movies = publishedMovies.map(function (movie :MovieMold, ..._) :MovieMold {
            return movie.scale(conf.scale);
        });
        mold.textureGroups = createTextureGroupMolds(atlases);
        return mold;
    }

    public function loadSWF (path :String, loader :Executor=null) :Future {
        const onComplete :FutureTask = new FutureTask();

        const swfFile :File = new File(path);
        const loadSwfFile :Future = Files.load(swfFile, loader);
        loadSwfFile.succeeded.add(function (data :ByteArray) :void {
            md5 = MD5.hashBytes(data);

            const loadSwf :Future = new SwfLoader().loadFromBytes(data);
            loadSwf.succeeded.add(function (loadedSwf :LoadedSwf) :void {
                swf = loadedSwf;
            });
            loadSwf.failed.add(function (error :Error) :void {
                addTopLevelError(ParseError.CRIT, error.message, error);
            });
            loadSwf.completed.add(onComplete.succeed);
        });
        loadSwfFile.failed.add(function (error :Error) :void {
            addTopLevelError(ParseError.CRIT, error.message, error);
            onComplete.fail(error);
        });

        return onComplete;
    }

    /**
     * @returns A list of paths to symbols in this library.
     */
    public function parseDocumentFile (fileData :ByteArray, path :String) :Vector.<String> {
        const xml :XML = Util.bytesToXML(fileData);
        frameRate = XmlUtil.getNumberAttr(xml, "frameRate", 24);

        const hex :String = XmlUtil.getStringAttr(xml, "backgroundColor", "#ffffff");
        backgroundColor = parseInt(hex.substr(1), 16);

        if (xml.media != null) {
            for each (var bitmap :XML in xml.media.DOMBitmapItem) {
                if (XmlUtil.getBooleanAttr(bitmap, "linkageExportForAS", false)) {
                    textures.push(new XflTexture(this, location, bitmap));
                }
            }
        }

        const paths :Vector.<String> = new <String>[];
        if (xml.symbols != null) {
            for each (var symbolXmlPath :XML in xml.symbols.Include) {
                paths.push("LIBRARY/" + XmlUtil.getStringAttr(symbolXmlPath, "href"));
            }
        }

        return paths;
    }

    public function parseLibraryFile (fileData :ByteArray, path :String) :void {
        const xml :XML = Util.bytesToXML(fileData);
        if (xml.name().localName != "DOMSymbolItem") {
            addTopLevelError(ParseError.DEBUG,
                "Skipping file since its root element isn't DOMSymbolItem");
            return;
        } else if (XmlUtil.getStringAttr(xml, "symbolType", "") == "graphic") {
            addTopLevelError(ParseError.DEBUG, "Skipping file because symbolType=graphic");
            return;
        }

        const isSprite :Boolean = XmlUtil.getBooleanAttr(xml, "isSpriteSubclass", false);
        log.debug("Parsing for library", "file", path, "isSprite", isSprite);
        try {
            if (isSprite) {
                // if "export in first frame" is not set, we won't be able to load the texture
                // from the swf.
                // TODO: remove this restriction by loading the entire swf before reading textures?
                if (!XmlUtil.getBooleanAttr(xml, "linkageExportInFirstFrame", true)) {
                    addError(location + ":" + XmlUtil.getStringAttr(xml, "linkageClassName"),
                        ParseError.CRIT, "\"Export in frame 1\" must be set");
                    return;
                }
                var texture :XflTexture = new XflTexture(this, location, xml);
                if (texture.isValid(swf)) textures.push(texture);
                else addError(location + ":" + texture.symbol, ParseError.CRIT, "Sprite is empty");

            } else {
                // It's a movie. If it's exported, we parse it now.
                // Else, we save it for possible parsing later.
                // (Un-exported movies that are not referenced will not be published.)
                if (XflMovie.isExported(xml)) parseMovie(xml);
                else _unexportedMovies.put(XflMovie.getName(xml), xml);
            }
        } catch (e :Error) {
            var type :String = isSprite ? "sprite" : "movie";
            addTopLevelError(ParseError.CRIT, "Unable to parse " + type + " in " + path, e);
            log.error("Unable to parse " + path, e);
        }
    }

    protected function parseMovie (xml :XML) :void {
        movies.push(XflMovie.parse(this, xml));
    }

    /** Creates TextureGroupMolds from a list of Atlases */
    protected static function createTextureGroupMolds (atlases :Vector.<Atlas>) :Vector.<TextureGroupMold> {
        const groups :Vector.<TextureGroupMold> = new <TextureGroupMold>[];
        function getGroup (scaleFactor :int) :TextureGroupMold {
            for each (var group :TextureGroupMold in groups) {
                if (group.scaleFactor == scaleFactor) {
                    return group;
                }
            }
            group = new TextureGroupMold();
            group.scaleFactor = scaleFactor;
            groups.push(group);
            return group;
        }

        for each (var atlas :Atlas in atlases) {
            var group :TextureGroupMold = getGroup(atlas.scaleFactor);
            group.atlases.push(atlas.toMold());
        }

        return groups;
    }

    /** Library name to XML for movies in the XFL that are not marked for export */
    protected const _unexportedMovies :Map = Maps.newMapOf(String);

    /** Object to symbol name for all exported textures and movies in the library */
    protected const _moldToSymbol :Map = Maps.newMapOf(Object);

    /** Library name to symbol or generated symbol for all textures and movies in the library */
    protected const _libraryNameToId :Map = Maps.newMapOf(String);

    /** Exported movies or movies used in exported movies. */
    protected const _toPublish :Set = Sets.newSetOf(MovieMold);

    /** Symbol or generated symbol to texture or movie. */
    protected const _idToItem :Dictionary = new Dictionary();

    protected const _errors :Vector.<ParseError> = new <ParseError>[];

    private static const log :Log = Log.getLog(XflLibrary);
}
}

//
// Flump - Copyright 2013 Flump Authors

package flump.xfl {

import flash.filesystem.File;
import flash.utils.ByteArray;
import flash.utils.Dictionary;

import com.adobe.crypto.MD5;

import flump.SwfTexture;
import flump.Util;
import flump.executor.Executor;
import flump.executor.Future;
import flump.executor.FutureTask;
import flump.executor.load.LoadedSwf;
import flump.executor.load.SwfLoader;
import flump.export.Atlas;
import flump.export.ExportConf;
import flump.export.Files;
import flump.mold.KeyframeMold;
import flump.mold.LayerMold;
import flump.mold.LibraryMold;
import flump.mold.MovieMold;
import flump.mold.TextureGroupMold;

import com.threerings.util.Log;
import com.threerings.util.Map;
import com.threerings.util.Maps;
import com.threerings.util.Set;
import com.threerings.util.Sets;
import com.threerings.util.XmlUtil;

public class XflLibrary
{
    use namespace xflns;

    /**
     * When an exported movie contains an unexported movie, it gets assigned a generated symbol
     * name with this prefix.
     */
    public static const IMPLICIT_PREFIX :String = "~";

    public var swf :LoadedSwf;

    public var frameRate :Number;
    public var backgroundColor :int;

    // The MD5 of the published library SWF
    public var md5 :String;

    public var location :String;

    public const movies :Vector.<MovieMold> = new <MovieMold>[];
    public const textures :Vector.<XflTexture> = new <XflTexture>[];

    public function XflLibrary (location :String) {
        this.location = location;
    }

    public function getItem (id :String, requiredType :Class=null) :* {
        const result :* = _idToItem[id];
        if (result === undefined) throw new Error("Unknown library item '" + id + "'");
        else if (requiredType != null) return requiredType(result);
        else return result;
    }

    public function isExported (movie :MovieMold) :Boolean {
        return _moldToSymbol.containsKey(movie);
    }

    public function get publishedMovies () :Vector.<MovieMold> {
        const result :Vector.<MovieMold> = new <MovieMold>[];
        for each (var movie :MovieMold in _toPublish.toArray().sortOn("id")) result.push(movie);
        return result;
    }

    public function finishLoading () :void {
        var movie :MovieMold = null;

        // Parse all un-exported movies that are referenced by the exported movies.
        for (var ii :int = 0; ii < movies.length; ++ii) {
            movie = movies[ii];
            for each (var symbolName :String in XflMovie.getSymbolNames(movie).toArray()) {
                var xml :XML = _unexportedMovies.remove(symbolName);
                if (xml != null) parseMovie(xml);
            }
        }

        for each (movie in movies) if (isExported(movie)) prepareForPublishing(movie);
    }

    protected function prepareForPublishing (movie :MovieMold) :void {
        if (!_toPublish.add(movie)) return;
        for each (var layer :LayerMold in movie.layers) {
            for each (var kf :KeyframeMold in layer.keyframes) {
                function adjustPivot (tex :SwfTexture) :void {
                    // Texture symbols have origins. For texture layer keyframes,
                    // we combine the texture's origin with the keyframe's pivot point.
                    kf.pivotX += tex.origin.x;
                    kf.pivotY += tex.origin.y;
                };
                if (movie.flipbook) {
                    adjustPivot(SwfTexture.fromFlipbook(this, movie, kf.index));
                } else {
                    if (kf.ref == null) continue;
                    kf.ref = _libraryNameToId.get(kf.ref);
                    var item :Object = _idToItem[kf.ref];
                    if (item == null) {
                        addTopLevelError(ParseError.CRIT,
                                "unrecognized library item '" + kf.ref + "'");
                    } else if (item is MovieMold) {
                        prepareForPublishing(MovieMold(item));
                    } else if (item is XflTexture) {
                        adjustPivot(SwfTexture.fromTexture(this.swf, XflTexture(item)));
                    }
                }
            }
        }
    }

    public function createId (item :Object, libraryName :String, symbol :String) :String {
        if (symbol != null) _moldToSymbol.put(item, symbol);
        const id :String = symbol == null ? IMPLICIT_PREFIX + libraryName : symbol;
        _libraryNameToId.put(libraryName, id);
        _idToItem[id] = item;
        return id;
    }

    public function getErrors (sev :String=null) :Vector.<ParseError> {
        if (sev == null) return _errors;
        const sevOrdinal :int = ParseError.severityToOrdinal(sev);
        return _errors.filter(function (err :ParseError, ..._) :Boolean {
            return err.sevOrdinal >= sevOrdinal;
        });
    }

    public function get valid () :Boolean { return getErrors(ParseError.CRIT).length == 0; }

    public function addTopLevelError (severity :String, message :String, e :Object=null) :void {
        addError(location, severity, message, e);
    }

    public function addError (location :String, severity :String, message :String, e :Object=null) :void {
        _errors.push(new ParseError(location, severity, message, e));
    }

    public function toJSONString (atlases :Vector.<Atlas>, conf :ExportConf, pretty :Boolean=false) :String {
        return JSON.stringify(toMold(atlases, conf), null, pretty ? "  " : null);
    }

    public function toMold (atlases :Vector.<Atlas>, conf :ExportConf) :LibraryMold {
        const mold :LibraryMold = new LibraryMold();
        mold.frameRate = frameRate;
        mold.md5 = md5;
        mold.movies = publishedMovies.map(function (movie :MovieMold, ..._) :MovieMold {
            return movie.scale(conf.scale);
        });
        mold.textureGroups = createTextureGroupMolds(atlases);
        return mold;
    }

    public function loadSWF (path :String, loader :Executor=null) :Future {
        const onComplete :FutureTask = new FutureTask();

        const swfFile :File = new File(path);
        const loadSwfFile :Future = Files.load(swfFile, loader);
        loadSwfFile.succeeded.add(function (data :ByteArray) :void {
            md5 = MD5.hashBytes(data);

            const loadSwf :Future = new SwfLoader().loadFromBytes(data);
            loadSwf.succeeded.add(function (loadedSwf :LoadedSwf) :void {
                swf = loadedSwf;
            });
            loadSwf.failed.add(function (error :Error) :void {
                addTopLevelError(ParseError.CRIT, error.message, error);
            });
            loadSwf.completed.add(onComplete.succeed);
        });
        loadSwfFile.failed.add(function (error :Error) :void {
            addTopLevelError(ParseError.CRIT, error.message, error);
            onComplete.fail(error);
        });

        return onComplete;
    }

    /**
     * @returns A list of paths to symbols in this library.
     */
    public function parseDocumentFile (fileData :ByteArray, path :String) :Vector.<String> {
        const xml :XML = Util.bytesToXML(fileData);
        frameRate = XmlUtil.getNumberAttr(xml, "frameRate", 24);

        const hex :String = XmlUtil.getStringAttr(xml, "backgroundColor", "#ffffff");
        backgroundColor = parseInt(hex.substr(1), 16);

        if (xml.media != null) {
            for each (var bitmap :XML in xml.media.DOMBitmapItem) {
                if (XmlUtil.getBooleanAttr(bitmap, "linkageExportForAS", false)) {
                    textures.push(new XflTexture(this, location, bitmap));
                }
            }
        }

        const paths :Vector.<String> = new <String>[];
        if (xml.symbols != null) {
            for each (var symbolXmlPath :XML in xml.symbols.Include) {
                paths.push("LIBRARY/" + XmlUtil.getStringAttr(symbolXmlPath, "href"));
            }
        }

        return paths;
    }

    public function parseLibraryFile (fileData :ByteArray, path :String) :void {
        const xml :XML = Util.bytesToXML(fileData);
        if (xml.name().localName != "DOMSymbolItem") {
            addTopLevelError(ParseError.DEBUG,
                "Skipping file since its root element isn't DOMSymbolItem");
            return;
        } else if (XmlUtil.getStringAttr(xml, "symbolType", "") == "graphic") {
            addTopLevelError(ParseError.DEBUG, "Skipping file because symbolType=graphic");
            return;
        }

        const isSprite :Boolean = XmlUtil.getBooleanAttr(xml, "isSpriteSubclass", false);
        log.debug("Parsing for library", "file", path, "isSprite", isSprite);
        try {
            if (isSprite) {
                // if "export in first frame" is not set, we won't be able to load the texture
                // from the swf.
                // TODO: remove this restriction by loading the entire swf before reading textures?
                if (!XmlUtil.getBooleanAttr(xml, "linkageExportInFirstFrame", true)) {
                    addError(location + ":" + XmlUtil.getStringAttr(xml, "linkageClassName"),
                        ParseError.CRIT, "\"Export in frame 1\" must be set");
                    return;
                }
                var texture :XflTexture = new XflTexture(this, location, xml);
                if (texture.isValid(swf)) textures.push(texture);
                else addError(location + ":" + texture.symbol, ParseError.CRIT, "Sprite is empty");

            } else {
                // It's a movie. If it's exported, we parse it now.
                // Else, we save it for possible parsing later.
                // (Un-exported movies that are not referenced will not be published.)
                if (XflMovie.isExported(xml)) parseMovie(xml);
                else _unexportedMovies.put(XflMovie.getName(xml), xml);
            }
        } catch (e :Error) {
            var type :String = isSprite ? "sprite" : "movie";
            addTopLevelError(ParseError.CRIT, "Unable to parse " + type + " in " + path, e);
            log.error("Unable to parse " + path, e);
        }
    }

    protected function parseMovie (xml :XML) :void {
        movies.push(XflMovie.parse(this, xml));
    }

    /** Creates TextureGroupMolds from a list of Atlases */
    protected static function createTextureGroupMolds (atlases :Vector.<Atlas>) :Vector.<TextureGroupMold> {
        const groups :Vector.<TextureGroupMold> = new <TextureGroupMold>[];
        function getGroup (scaleFactor :int) :TextureGroupMold {
            for each (var group :TextureGroupMold in groups) {
                if (group.scaleFactor == scaleFactor) {
                    return group;
                }
            }
            group = new TextureGroupMold();
            group.scaleFactor = scaleFactor;
            groups.push(group);
            return group;
        }

        for each (var atlas :Atlas in atlases) {
            var group :TextureGroupMold = getGroup(atlas.scaleFactor);
            group.atlases.push(atlas.toMold());
        }

        return groups;
    }

    /** Library name to XML for movies in the XFL that are not marked for export */
    protected const _unexportedMovies :Map = Maps.newMapOf(String);

    /** Object to symbol name for all exported textures and movies in the library */
    protected const _moldToSymbol :Map = Maps.newMapOf(Object);

    /** Library name to symbol or generated symbol for all textures and movies in the library */
    protected const _libraryNameToId :Map = Maps.newMapOf(String);

    /** Exported movies or movies used in exported movies. */
    protected const _toPublish :Set = Sets.newSetOf(MovieMold);

    /** Symbol or generated symbol to texture or movie. */
    protected const _idToItem :Dictionary = new Dictionary();

    protected const _errors :Vector.<ParseError> = new <ParseError>[];

    private static const log :Log = Log.getLog(XflLibrary);
}
}

Apply the keyframe pivot to flipbooks too.

Fixes #36. @tconkling, could you please verify and do an exporter
release?


/*

as3isolib - An open-source ActionScript 3.0 Isometric Library developed to assist
in creating isometrically projected content (such as games and graphics)
targeted for the Flash player platform

http://code.google.com/p/as3isolib/

Copyright (c) 2006 - 2008 J.W.Opitz, All Rights Reserved.

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

*/
package as3isolib.display.scene
{

	import as3isolib.bounds.IBounds;
	import as3isolib.bounds.SceneBounds;
	import as3isolib.core.IIsoDisplayObject;
	import as3isolib.core.IsoContainer;
	import as3isolib.core.as3isolib_internal;
	import as3isolib.data.INode;
	import as3isolib.display.renderers.DefaultSceneLayoutRenderer;
	import as3isolib.display.renderers.ISceneLayoutRenderer;
	import as3isolib.display.renderers.ISceneRenderer;
	import as3isolib.events.IsoEvent;

	import flash.display.DisplayObjectContainer;

	import mx.core.ClassFactory;
	import mx.core.IFactory;

	use namespace as3isolib_internal;

	/**
	 * IsoScene is a base class for grouping and rendering IIsoDisplayObject children according to their isometric position-based depth.
	 */
	public class IsoScene extends IsoContainer implements IIsoScene
	{
		///////////////////////////////////////////////////////////////////////////////
		//	BOUNDS
		///////////////////////////////////////////////////////////////////////////////

		/**
		 * @private
		 */
		private var _isoBounds:IBounds;

		/**
		 * @inheritDoc
		 */
		public function get isoBounds ():IBounds
		{
			/* if (!_isoBounds || isInvalidated)
				_isoBounds =  */

			return new SceneBounds(this);
		}

		///////////////////////////////////////////////////////////////////////////////
		//	HOST CONTAINER
		///////////////////////////////////////////////////////////////////////////////

		/**
		 * @private
		 */
		protected var host:DisplayObjectContainer;

		as3isolib_internal

		/**
		 * @private
		 */
		public function get hostContainer ():DisplayObjectContainer
		{
			return host;
		}

		/**
		 * @inheritDoc
		 */
		public function set hostContainer (value:DisplayObjectContainer):void
		{
			if (value && host != value)
			{
				if (host && host.contains(container))
				{
					host.removeChild(container);
					ownerObject = null;
				}

				else if (hasParent)
					parent.removeChild(this);

				host = value;
				if (host)
				{
					host.addChild(container);
					ownerObject = host;
					parentNode = null;
				}
			}
		}

		///////////////////////////////////////////////////////////////////////////////
		//	INVALIDATE CHILDREN
		///////////////////////////////////////////////////////////////////////////////

		/**
		 * @private
		 *
		 * Array of invalidated children.  Each child dispatches an IsoEvent.INVALIDATION event which notifies
		 * the scene that that particular child is invalidated and subsequentally the scene is also invalidated.
		 */
		protected var invalidatedChildrenArray:Array = [];

		/**
		 * @inheritDoc
		 */
		public function get invalidatedChildren ():Array
		{
			return invalidatedChildrenArray;
		}

		///////////////////////////////////////////////////////////////////////////////
		//	OVERRIDES
		///////////////////////////////////////////////////////////////////////////////

		/**
		 * @inheritDoc
		 */
		override public function addChildAt (child:INode, index:uint):void
		{
			if (child is IIsoDisplayObject)
			{
				super.addChildAt(child, index);
				child.addEventListener(IsoEvent.INVALIDATE, child_invalidateHandler);

				bIsInvalidated = true; //since the child most likely had fired an invalidation event prior to being added, manually invalidate the scene
			}

			else
				throw new Error ("parameter child is not of type IIsoDisplayObject");
		}

		/**
		 * @inheritDoc
		 */
		override public function setChildIndex (child:INode, index:uint):void
		{
			super.setChildIndex(child, index);
			bIsInvalidated = true;
		}

		/**
		 * @inheritDoc
		 */
		override public function removeChildByID (id:String):INode
		{
			var child:INode = super.removeChildByID(id);
			if (child)
			{
				child.removeEventListener(IsoEvent.INVALIDATE, child_invalidateHandler);
				bIsInvalidated = true;
			}

			return child;
		}

		/**
		 * @inheritDoc
		 */
		override public function removeAllChildren ():void
		{
			var child:INode
			for each (child in children)
				child.removeEventListener(IsoEvent.INVALIDATE, child_invalidateHandler);

			super.removeAllChildren();
			bIsInvalidated = true;
		}

		/**
		 * Renders the scene as invalidated if a child object is invalidated.
		 *
		 * @param evt The IsoEvent dispatched from the invalidated child.
		 */
		protected function child_invalidateHandler (evt:IsoEvent):void
		{
			var child:Object = evt.target;
			if (invalidatedChildrenArray.indexOf(child) == -1)
				invalidatedChildrenArray.push(child);

			bIsInvalidated = true;
		}

		///////////////////////////////////////////////////////////////////////////////
		//	LAYOUT RENDERER
		///////////////////////////////////////////////////////////////////////////////

		/**
		 * Flags the scene for possible layout rendering.
		 * If false, child objects are sorted by the order they were added rather than by their isometric depth.
		 */
		public var layoutEnabled:Boolean = true;

		private var layoutRendererFactory:IFactory;

		/**
		 * @private
		 */
		public function get layoutRenderer ():IFactory
		{
			return layoutRendererFactory;
		}

		/**
		 * The factory used to create the ISceneLayoutRenderer responsible for the layout of the child objects.
		 */
		public function set layoutRenderer (value:IFactory):void
		{
			if (value && layoutRendererFactory != value)
			{
				layoutRendererFactory = value;
				bIsInvalidated = true;
			}
		}

		///////////////////////////////////////////////////////////////////////////////
		//	STYLE RENDERERS
		///////////////////////////////////////////////////////////////////////////////

		/**
		 * Flags the scene for possible style rendering.
		 */
		public var stylingEnabled:Boolean = true;

		private var styleRendererFactories:Array = [];

		/**
		 * @private
		 */
		public function get styleRenderers ():Array
		{
			return styleRendererFactories;
		}

		/**
		 * An array of IFactories whose class generators are ISceneRenderer.
		 * If any value contained within the array is not of type IFactory, it will be ignored.
		 */
		public function set styleRenderers (value:Array):void
		{
			if (value)
			{
				var temp:Array = [];
				var obj:Object;
				for each (obj in value)
				{
					if (obj is IFactory)
						temp.push(obj);
				}

				styleRendererFactories = temp;
				bIsInvalidated = true;
			}
		}

		///////////////////////////////////////////////////////////////////////////////
		//	INVALIDATION
		///////////////////////////////////////////////////////////////////////////////

		/**
		 * Flags the scene as invalidated during the rendering process
		 */
		public function invalidateScene ():void
		{
			bIsInvalidated = true;
		}

		///////////////////////////////////////////////////////////////////////////////
		//	RENDER
		///////////////////////////////////////////////////////////////////////////////

		/**
		 * @inheritDoc
		 */
		override protected function renderLogic (recursive:Boolean = true):void
		{
			super.renderLogic(recursive); //push individual changes thru, then sort based on new visible content of each child

			if (bIsInvalidated)
			{
				//render the layout first
				var sceneLayoutRenderer:ISceneLayoutRenderer;
				if (layoutEnabled)
				{
					sceneLayoutRenderer = layoutRendererFactory.newInstance();
					if (sceneLayoutRenderer)
						sceneLayoutRenderer.renderScene(this);
				}

				//apply styling
				mainContainer.graphics.clear(); //should we do this here?

				var sceneRenderer:ISceneRenderer;
				var factory:IFactory
				if (stylingEnabled)
				{
					for each (factory in styleRendererFactories)
					{
						sceneRenderer = factory.newInstance();
						if (sceneRenderer)
							sceneRenderer.renderScene(this);
					}
				}

				bIsInvalidated = false;
			}
		}

		/**
		 * @inheritDoc
		 */
		override protected function postRenderLogic ():void
		{
			invalidatedChildrenArray = [];

			super.postRenderLogic();
			//should we still call sceneRendered()?
			sceneRendered();
		}

		/**
		 * This function has been deprecated.  Please refer to postRenderLogic.
		 */
		protected function sceneRendered ():void
		{
		}

		///////////////////////////////////////////////////////////////////////////////
		//	CONSTRUCTOR
		///////////////////////////////////////////////////////////////////////////////

		/**
		 * Constructor
		 */
		public function IsoScene ()
		{
			super();

			layoutRendererFactory = new ClassFactory(DefaultSceneLayoutRenderer);
		}
	}
}

/*

as3isolib - An open-source ActionScript 3.0 Isometric Library developed to assist
in creating isometrically projected content (such as games and graphics)
targeted for the Flash player platform

http://code.google.com/p/as3isolib/

Copyright (c) 2006 - 2008 J.W.Opitz, All Rights Reserved.

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

*/
package as3isolib.display.scene
{

	import as3isolib.bounds.IBounds;
	import as3isolib.bounds.SceneBounds;
	import as3isolib.core.IIsoDisplayObject;
	import as3isolib.core.IsoContainer;
	import as3isolib.core.as3isolib_internal;
	import as3isolib.data.INode;
	import as3isolib.display.renderers.DefaultSceneLayoutRenderer;
	import as3isolib.display.renderers.ISceneLayoutRenderer;
	import as3isolib.display.renderers.ISceneRenderer;
	import as3isolib.events.IsoEvent;

	import flash.display.DisplayObjectContainer;

	import mx.core.ClassFactory;
	import mx.core.IFactory;

	use namespace as3isolib_internal;

	/**
	 * IsoScene is a base class for grouping and rendering IIsoDisplayObject children according to their isometric position-based depth.
	 */
	public class IsoScene extends IsoContainer implements IIsoScene
	{
		///////////////////////////////////////////////////////////////////////////////
		//	BOUNDS
		///////////////////////////////////////////////////////////////////////////////

		/**
		 * @private
		 */
		private var _isoBounds:IBounds;

		/**
		 * @inheritDoc
		 */
		public function get isoBounds ():IBounds
		{
			/* if (!_isoBounds || isInvalidated)
				_isoBounds =  */

			return new SceneBounds(this);
		}

		///////////////////////////////////////////////////////////////////////////////
		//	HOST CONTAINER
		///////////////////////////////////////////////////////////////////////////////

		/**
		 * @private
		 */
		protected var host:DisplayObjectContainer;

		as3isolib_internal

		/**
		 * @private
		 */
		public function get hostContainer ():DisplayObjectContainer
		{
			return host;
		}

		/**
		 * @inheritDoc
		 */
		public function set hostContainer (value:DisplayObjectContainer):void
		{
			if (value && host != value)
			{
				if (host && host.contains(container))
				{
					host.removeChild(container);
					ownerObject = null;
				}

				else if (hasParent)
					parent.removeChild(this);

				host = value;
				if (host)
				{
					host.addChild(container);
					ownerObject = host;
					parentNode = null;
				}
			}
		}

		///////////////////////////////////////////////////////////////////////////////
		//	INVALIDATE CHILDREN
		///////////////////////////////////////////////////////////////////////////////

		/**
		 * @private
		 *
		 * Array of invalidated children.  Each child dispatches an IsoEvent.INVALIDATION event which notifies
		 * the scene that that particular child is invalidated and subsequentally the scene is also invalidated.
		 */
		protected var invalidatedChildrenArray:Array = [];

		/**
		 * @inheritDoc
		 */
		public function get invalidatedChildren ():Array
		{
			return invalidatedChildrenArray;
		}

		///////////////////////////////////////////////////////////////////////////////
		//	OVERRIDES
		///////////////////////////////////////////////////////////////////////////////

		/**
		 * @inheritDoc
		 */
		override public function addChildAt (child:INode, index:uint):void
		{
			if (child is IIsoDisplayObject)
			{
				super.addChildAt(child, index);
				child.addEventListener(IsoEvent.INVALIDATE, child_invalidateHandler);

				bIsInvalidated = true; //since the child most likely had fired an invalidation event prior to being added, manually invalidate the scene
			}

			else
				throw new Error ("parameter child is not of type IIsoDisplayObject");
		}

		/**
		 * @inheritDoc
		 */
		override public function setChildIndex (child:INode, index:uint):void
		{
			super.setChildIndex(child, index);
			bIsInvalidated = true;
		}

		/**
		 * @inheritDoc
		 */
		override public function removeChildByID (id:String):INode
		{
			var child:INode = super.removeChildByID(id);
			if (child)
			{
				child.removeEventListener(IsoEvent.INVALIDATE, child_invalidateHandler);
				bIsInvalidated = true;
			}

			return child;
		}

		/**
		 * @inheritDoc
		 */
		override public function removeAllChildren ():void
		{
			var child:INode
			for each (child in children)
				child.removeEventListener(IsoEvent.INVALIDATE, child_invalidateHandler);

			super.removeAllChildren();
			bIsInvalidated = true;
		}

		/**
		 * Renders the scene as invalidated if a child object is invalidated.
		 *
		 * @param evt The IsoEvent dispatched from the invalidated child.
		 */
		protected function child_invalidateHandler (evt:IsoEvent):void
		{
			var child:Object = evt.target;
			if (invalidatedChildrenArray.indexOf(child) == -1)
				invalidatedChildrenArray.push(child);

			bIsInvalidated = true;
		}

		///////////////////////////////////////////////////////////////////////////////
		//	LAYOUT RENDERER
		///////////////////////////////////////////////////////////////////////////////

		/**
		 * Flags the scene for possible layout rendering.
		 * If false, child objects are sorted by the order they were added rather than by their isometric depth.
		 */
		public var layoutEnabled:Boolean = true;

		private var layoutRendererFactory:IFactory;

		/**
		 * @private
		 */
		public function get layoutRenderer ():IFactory
		{
			return layoutRendererFactory;
		}

		/**
		 * The factory used to create the ISceneLayoutRenderer responsible for the layout of the child objects.
		 */
		public function set layoutRenderer (value:IFactory):void
		{
			if (value && layoutRendererFactory != value)
			{
				layoutRendererFactory = value;
				bIsInvalidated = true;
			}
		}

		///////////////////////////////////////////////////////////////////////////////
		//	STYLE RENDERERS
		///////////////////////////////////////////////////////////////////////////////

		/**
		 * Flags the scene for possible style rendering.
		 */
		public var stylingEnabled:Boolean = true;

		private var styleRendererFactories:Array = [];

		/**
		 * @private
		 */
		public function get styleRenderers ():Array
		{
			return styleRendererFactories;
		}

		/**
		 * An array of IFactories whose class generators are ISceneRenderer.
		 * If any value contained within the array is not of type IFactory, it will be ignored.
		 */
		public function set styleRenderers (value:Array):void
		{
			if (value)
			{
				var temp:Array = [];
				var obj:Object;
				for each (obj in value)
				{
					if (obj is IFactory)
						temp.push(obj);
				}

				styleRendererFactories = temp;
				bIsInvalidated = true;
			}
		}

		///////////////////////////////////////////////////////////////////////////////
		//	INVALIDATION
		///////////////////////////////////////////////////////////////////////////////

		/**
		 * Flags the scene as invalidated during the rendering process
		 */
		public function invalidateScene ():void
		{
			bIsInvalidated = true;
		}

		///////////////////////////////////////////////////////////////////////////////
		//	RENDER
		///////////////////////////////////////////////////////////////////////////////

		/**
		 * @inheritDoc
		 */
		override protected function renderLogic (recursive:Boolean = true):void
		{
			super.renderLogic(recursive); //push individual changes thru, then sort based on new visible content of each child

			if (bIsInvalidated)
			{
				//render the layout first
				var sceneLayoutRenderer:ISceneLayoutRenderer;
				if (layoutEnabled)
				{
					sceneLayoutRenderer = layoutRendererFactory.newInstance();
					if (sceneLayoutRenderer)
						sceneLayoutRenderer.renderScene(this);
				}

				//fix for bug #20 - http://code.google.com/p/as3isolib/issues/detail?id=20
				var sceneRenderer:ISceneRenderer;
				var factory:IFactory
				if (stylingEnabled && styleRendererFactories.length > 0)
				{
					mainContainer.graphics.clear();

					for each (factory in styleRendererFactories)
					{
						sceneRenderer = factory.newInstance();
						if (sceneRenderer)
							sceneRenderer.renderScene(this);
					}
				}

				bIsInvalidated = false;
			}
		}

		/**
		 * @inheritDoc
		 */
		override protected function postRenderLogic ():void
		{
			invalidatedChildrenArray = [];

			super.postRenderLogic();
			//should we still call sceneRendered()?
			sceneRendered();
		}

		/**
		 * This function has been deprecated.  Please refer to postRenderLogic.
		 */
		protected function sceneRendered ():void
		{
		}

		///////////////////////////////////////////////////////////////////////////////
		//	CONSTRUCTOR
		///////////////////////////////////////////////////////////////////////////////

		/**
		 * Constructor
		 */
		public function IsoScene ()
		{
			super();

			layoutRendererFactory = new ClassFactory(DefaultSceneLayoutRenderer);
		}
	}
}


package krewfw.utils.as3 {

    /**
     * Flexible asynchronous tasker.
     *
     * Usage:
     *

     *     //--- Basic sequential task
     *     var async:KrewAsync = new KrewAsync({
     *         serial : [function_1, function_2, function_3],
     *         error  : _onErrorHandler,
     *         anyway : _onFinallyHandler
     *     });
     *     async.go();
     *
     *
     *     //--- Parallel task
     *     var async:KrewAsync = new KrewAsync({
     *         parallel: [function_1, function_2, function_3],
     *         error   : _onErrorHandler,
     *         anyway  : _onFinallyHandler
     *     });
     *     async.go();
     *
     *     * Throws error if both 'serial' and 'parallel' are specified.
     *
     *
     *     //--- Function receives KrewAsync instance,
     *     //    and you should call done() or fail().
     +
     *     var async:KrewAsync = new KrewAsync({
     *         serial: [
     *             function(async:KrewAsync):void {
     *                 if (TASK_IS_SUCCEEDED) {
     *                     async.done();
     *                 } else {
     *                     async.fail();
     *                 }
     *             }
     *         ]
     *     });
     *
     *
     *     //--- Sub task
     *     var async:KrewAsync = new KrewAsync({
     *         serial: [
     *             function_1,
     *             function_2,
     *             {parallel: [
     *                 function_3,
     *                 function_4,
     *                 {serial: [
     *                     function_5,
     *                     function_6
     *                 ]}
     *             ]},
     *             function_7
     *         ],
     *         error  : _onErrorHandler,
     *         anyway : _onFinallyHandler
     *     });
     *     async.go();
     *
     *     [Sequence]:
     *                   |3 ------>|
     *                   |         |
     *         1 -> 2 -> |4 ------>| -> 7 -> anyway
     *                   |         |
     *                   |5 -> 6 ->|
     *
     *

     *
     * @param asyncDef Object or Function or instance of KrewAsync. Example:
     *

     *     var async:KrewAsync = new KrewAsync(<asyncDef>);
     *
     *     <asyncDef> ::= {
     *         single  : function(async:KrewAsync):void {}  // KrewAsync uses internally
     *         // OR
     *         serial  : [<asyncDef>, ... ]
     *         // OR
     *         parallel: [<asyncDef>, ... ]
     *
     *         error   : function():void {},  // optional
     *         anyway  : function():void {}   // optional
     *     }
     *
     *     * If <asyncDef> == Function, then it is converted into:
     *         {single: Function}
     *
     *     * You can use class instances instead of asyncDef object:
     *
     *         public class MyKrewAsyncTask extends KrewAsync {
     *             public function MyKrewAsyncTask() {
     *                 super({
     *                     parallel: [method_1, method_2, method_3]
     *                 });
     *             }
     *         }
     *
     *         var async:KrewAsync = new KrewAsync({
     *             serial: [
     *                 function_1,
     *                 new MyKrewAsyncTask(),
     *                 function_2
     *             ]
     *         });
     *

     */
    //------------------------------------------------------------
    public class KrewAsync {

        private var _myTask:Function;
        private var _errorHandler:Function;
        private var _successHandler:Function;
        private var _finallyHandler:Function;

        private var _serialTasks  :Vector.<KrewAsync>;
        private var _parallelTasks:Vector.<KrewAsync>;

        private var _serialTaskIndex:int = 0;
        private var _onComplete:Function = function():void {};

        public static const UNDEF   :int = 1;
        public static const RESOLVED:int = 2;
        public static const REJECTED:int = 3;

        private var _state:int = KrewAsync.UNDEF;

        private var _isResolved :Boolean = false;
        private var _isRejected :Boolean = false;
        private var _isFinalized:Boolean = false;

        //------------------------------------------------------------
        public function KrewAsync(asyncDef:*) {
            if (asyncDef is Function) {
                _initWithFunction(asyncDef);
                return;
            }
            if (asyncDef is KrewAsync) {
                _initWithKrewAsync(asyncDef);
                return;
            }
            if (asyncDef is Array) {
                _initWithArray(asyncDef);
                return;
            }
            if (asyncDef is Object) {
                _initWithObject(asyncDef);
                return;
            }
        }

        //------------------------------------------------------------
        // accessors
        //------------------------------------------------------------

        public function get myTask()        :Function { return _myTask; }
        public function get errorHandler()  :Function { return _errorHandler; }
        public function get successHandler():Function { return _successHandler; }
        public function get finallyHandler():Function { return _finallyHandler; }

        public function get serialTasks()  :Vector.<KrewAsync> { return _serialTasks; }
        public function get parallelTasks():Vector.<KrewAsync> { return _parallelTasks; }

        public function get state():int { return _state; }

        //------------------------------------------------------------
        // public
        //------------------------------------------------------------

        public function go(onComplete:Function=null):void {
            if (onComplete != null) {
                _onComplete = onComplete;
            }

            if (_myTask != null) {
                _myTask(this);
                return;
            }

            if (_serialTasks != null) {
                _kickNextSerialTask();
                return;
            }

            if (_parallelTasks != null) {
                _kickParallelTasks();
                return;
            }
        }

        public function done():void {
            _state = KrewAsync.RESOLVED;
            _finalize();
        }

        public function fail():void {
            _state = KrewAsync.REJECTED;
            _finalize();
        }

        //------------------------------------------------------------
        // initializer
        //------------------------------------------------------------

        private function _initWithObject(asyncDef:Object):void {
            _validateInitObject(asyncDef);

            if (asyncDef.single   != null) { _myTask = asyncDef.single; }
            if (asyncDef.serial   != null) { _serialTasks   = _makeChildren(asyncDef.serial); }
            if (asyncDef.parallel != null) { _parallelTasks = _makeChildren(asyncDef.parallel); }

            if (asyncDef.success  != null) { _successHandler = asyncDef.success; }
            if (asyncDef.error    != null) { _errorHandler   = asyncDef.error; }
            if (asyncDef.anyway   != null) { _finallyHandler = asyncDef.anyway; }
        }

        private function _validateInitObject(asyncDef:Object):void {
            var exclusiveDefCount:uint = 0;
            if (asyncDef.single   != null) { ++exclusiveDefCount; }
            if (asyncDef.serial   != null) { ++exclusiveDefCount; }
            if (asyncDef.parallel != null) { ++exclusiveDefCount; }

            if (exclusiveDefCount != 1) {
                throw new Error("[KrewAsync] Error: Invalid async task definition.");
            }
        }

        private function _makeChildren(asyncDefList:Array):Vector.<KrewAsync> {
            var children:Vector.<KrewAsync> = new Vector.<KrewAsync>;

            for each (var def:* in asyncDefList) {
                var async:KrewAsync = (def is KrewAsync) ? def : new KrewAsync(def);
                children.push(async);
            }
            return children;
        }

        private function _initWithFunction(asyncDef:Function):void {
            _initWithObject({
                single: asyncDef
            });
        }

        private function _initWithArray(asyncDef:Array):void {
            _initWithObject({
                serial: asyncDef
            });
        }

        private function _initWithKrewAsync(asyncDef:KrewAsync):void {
            _myTask         = asyncDef.myTask;
            _successHandler = asyncDef.successHandler;
            _errorHandler   = asyncDef.errorHandler;
            _finallyHandler = asyncDef.finallyHandler;
            _serialTasks    = asyncDef.serialTasks;
            _parallelTasks  = asyncDef.parallelTasks;
        }

        //------------------------------------------------------------
        // task runner
        //------------------------------------------------------------

        private function _kickNextSerialTask():void {
            if (_serialTaskIndex >= _serialTasks.length) {
                _onResolve();
                return;
            }

            var nextTask:KrewAsync = _serialTasks[_serialTaskIndex];
            ++_serialTaskIndex;

            nextTask.go(function(async:KrewAsync):void {
                if (async.state == KrewAsync.RESOLVED) {
                    _kickNextSerialTask();
                } else {
                    _onReject();
                }
            });
        }

        private function _kickParallelTasks():void {
            var doneCount:int = 0;

            for each (var task:KrewAsync in _parallelTasks) {
                task.go(function(async:KrewAsync):void {
                    if (async.state == KrewAsync.RESOLVED) {
                        ++doneCount;
                        if (doneCount == _parallelTasks.length) {
                            _onResolve();
                        }
                    } else {
                        _onReject();
                    }
                });
            }
        }

        //------------------------------------------------------------
        // result handler
        //------------------------------------------------------------

        private function _onResolve():void {
            if (_isResolved) { return; }
            _isResolved = true;

            if (_successHandler != null) {
                _successHandler();
            }
            done();
        }

        private function _onReject():void {
            if (_isRejected) { return; }
            _isRejected = true;

            if (_errorHandler != null) {
                _errorHandler();
            }
            fail();
        }

        private function _finalize():void {
            if (_isFinalized) { return; }
            _isFinalized = true;

            if (_finallyHandler != null) {
                _finallyHandler();
            }
            _onComplete(this);
        }

    }
}

package krewfw.utils.as3 {

    /**
     * Flexible asynchronous tasker.
     * @see http://docs.tatsuya-koyama.com/krew-framework/samples/krewasync/
     *
     * Usage:
     *

     *     //--- Basic sequential task
     *     var async:KrewAsync = new KrewAsync({
     *         serial : [function_1, function_2, function_3],
     *         error  : _onErrorHandler,
     *         anyway : _onFinallyHandler
     *     });
     *     async.go();
     *
     *
     *     //--- Parallel task
     *     var async:KrewAsync = new KrewAsync({
     *         parallel: [function_1, function_2, function_3],
     *         error   : _onErrorHandler,
     *         anyway  : _onFinallyHandler
     *     });
     *     async.go();
     *
     *     * Throws error if both 'serial' and 'parallel' are specified.
     *
     *
     *     //--- Function receives KrewAsync instance,
     *     //    and you should call done() or fail().
     +
     *     var async:KrewAsync = new KrewAsync({
     *         serial: [
     *             function(async:KrewAsync):void {
     *                 if (TASK_IS_SUCCEEDED) {
     *                     async.done();
     *                 } else {
     *                     async.fail();
     *                 }
     *             }
     *         ]
     *     });
     *
     *
     *     //--- Sub task
     *     var async:KrewAsync = new KrewAsync({
     *         serial: [
     *             function_1,
     *             function_2,
     *             {parallel: [
     *                 function_3,
     *                 function_4,
     *                 {serial: [
     *                     function_5,
     *                     function_6
     *                 ]}
     *             ]},
     *             function_7
     *         ],
     *         success: _onSuccessHandler,
     *         error  : _onErrorHandler,
     *         anyway : _onFinallyHandler
     *     });
     *     async.go();
     *
     *     [Sequence]:
     *                   |3 ------>|
     *                   |         |
     *         1 -> 2 -> |4 ------>| -> 7 -> success -> anyway
     *                   |         |        (or error)
     *                   |5 -> 6 ->|
     *
     *

     *
     * @param asyncDef Object or Function or instance of KrewAsync. Example:
     *

     *     var async:KrewAsync = new KrewAsync(<asyncDef>);
     *
     *     <asyncDef> ::= {
     *         single  : function(async:KrewAsync):void {}  // KrewAsync uses internally
     *         // OR
     *         serial  : [<asyncDef>, ... ]
     *         // OR
     *         parallel: [<asyncDef>, ... ]
     *
     *         success : function():void {},  // optional
     *         error   : function():void {},  // optional
     *         anyway  : function():void {}   // optional
     *     }
     *
     *     * If <asyncDef> == Function, then it is converted into:
     *         {single: Function}
     *
     *     * You can use class instances instead of asyncDef object:
     *
     *         public class MyKrewAsyncTask extends KrewAsync {
     *             public function MyKrewAsyncTask() {
     *                 super({
     *                     parallel: [method_1, method_2, method_3]
     *                 });
     *             }
     *         }
     *
     *         var async:KrewAsync = new KrewAsync({
     *             serial: [
     *                 function_1,
     *                 new MyKrewAsyncTask(),
     *                 function_2
     *             ]
     *         });
     *

     */
    //------------------------------------------------------------
    public class KrewAsync {

        private var _myTask:Function;
        private var _errorHandler:Function;
        private var _successHandler:Function;
        private var _finallyHandler:Function;

        private var _serialTasks  :Vector.<KrewAsync>;
        private var _parallelTasks:Vector.<KrewAsync>;

        private var _serialTaskIndex:int = 0;
        private var _onComplete:Function = function():void {};

        public static const UNDEF   :int = 1;
        public static const RESOLVED:int = 2;
        public static const REJECTED:int = 3;

        private var _state:int = KrewAsync.UNDEF;

        private var _isResolved :Boolean = false;
        private var _isRejected :Boolean = false;
        private var _isFinalized:Boolean = false;

        //------------------------------------------------------------
        public function KrewAsync(asyncDef:*) {
            if (asyncDef is Function) {
                _initWithFunction(asyncDef);
                return;
            }
            if (asyncDef is KrewAsync) {
                _initWithKrewAsync(asyncDef);
                return;
            }
            if (asyncDef is Array) {
                _initWithArray(asyncDef);
                return;
            }
            if (asyncDef is Object) {
                _initWithObject(asyncDef);
                return;
            }
        }

        //------------------------------------------------------------
        // accessors
        //------------------------------------------------------------

        public function get myTask()        :Function { return _myTask; }
        public function get errorHandler()  :Function { return _errorHandler; }
        public function get successHandler():Function { return _successHandler; }
        public function get finallyHandler():Function { return _finallyHandler; }

        public function get serialTasks()  :Vector.<KrewAsync> { return _serialTasks; }
        public function get parallelTasks():Vector.<KrewAsync> { return _parallelTasks; }

        public function get state():int { return _state; }

        //------------------------------------------------------------
        // public
        //------------------------------------------------------------

        public function go(onComplete:Function=null):void {
            if (onComplete != null) {
                _onComplete = onComplete;
            }

            if (_myTask != null) {
                _myTask(this);
                return;
            }

            if (_serialTasks != null) {
                _kickNextSerialTask();
                return;
            }

            if (_parallelTasks != null) {
                _kickParallelTasks();
                return;
            }
        }

        public function done():void {
            _state = KrewAsync.RESOLVED;
            _finalize();
        }

        public function fail():void {
            _state = KrewAsync.REJECTED;
            _finalize();
        }

        //------------------------------------------------------------
        // initializer
        //------------------------------------------------------------

        private function _initWithObject(asyncDef:Object):void {
            _validateInitObject(asyncDef);

            if (asyncDef.single   != null) { _myTask = asyncDef.single; }
            if (asyncDef.serial   != null) { _serialTasks   = _makeChildren(asyncDef.serial); }
            if (asyncDef.parallel != null) { _parallelTasks = _makeChildren(asyncDef.parallel); }

            if (asyncDef.success  != null) { _successHandler = asyncDef.success; }
            if (asyncDef.error    != null) { _errorHandler   = asyncDef.error; }
            if (asyncDef.anyway   != null) { _finallyHandler = asyncDef.anyway; }
        }

        private function _validateInitObject(asyncDef:Object):void {
            var exclusiveDefCount:uint = 0;
            if (asyncDef.single   != null) { ++exclusiveDefCount; }
            if (asyncDef.serial   != null) { ++exclusiveDefCount; }
            if (asyncDef.parallel != null) { ++exclusiveDefCount; }

            if (exclusiveDefCount != 1) {
                throw new Error("[KrewAsync] Error: Invalid async task definition.");
            }
        }

        private function _makeChildren(asyncDefList:Array):Vector.<KrewAsync> {
            var children:Vector.<KrewAsync> = new Vector.<KrewAsync>;

            for each (var def:* in asyncDefList) {
                var async:KrewAsync = (def is KrewAsync) ? def : new KrewAsync(def);
                children.push(async);
            }
            return children;
        }

        private function _initWithFunction(asyncDef:Function):void {
            _initWithObject({
                single: asyncDef
            });
        }

        private function _initWithArray(asyncDef:Array):void {
            _initWithObject({
                serial: asyncDef
            });
        }

        private function _initWithKrewAsync(asyncDef:KrewAsync):void {
            _myTask         = asyncDef.myTask;
            _successHandler = asyncDef.successHandler;
            _errorHandler   = asyncDef.errorHandler;
            _finallyHandler = asyncDef.finallyHandler;
            _serialTasks    = asyncDef.serialTasks;
            _parallelTasks  = asyncDef.parallelTasks;
        }

        //------------------------------------------------------------
        // task runner
        //------------------------------------------------------------

        private function _kickNextSerialTask():void {
            if (_serialTaskIndex >= _serialTasks.length) {
                _onResolve();
                return;
            }

            var nextTask:KrewAsync = _serialTasks[_serialTaskIndex];
            ++_serialTaskIndex;

            nextTask.go(function(async:KrewAsync):void {
                if (async.state == KrewAsync.RESOLVED) {
                    _kickNextSerialTask();
                } else {
                    _onReject();
                }
            });
        }

        private function _kickParallelTasks():void {
            var doneCount:int = 0;

            for each (var task:KrewAsync in _parallelTasks) {
                task.go(function(async:KrewAsync):void {
                    if (async.state == KrewAsync.RESOLVED) {
                        ++doneCount;
                        if (doneCount == _parallelTasks.length) {
                            _onResolve();
                        }
                    } else {
                        _onReject();
                    }
                });
            }
        }

        //------------------------------------------------------------
        // result handler
        //------------------------------------------------------------

        private function _onResolve():void {
            if (_isResolved) { return; }
            _isResolved = true;

            if (_successHandler != null) {
                _successHandler();
            }
            done();
        }

        private function _onReject():void {
            if (_isRejected) { return; }
            _isRejected = true;

            if (_errorHandler != null) {
                _errorHandler();
            }
            fail();
        }

        private function _finalize():void {
            if (_isFinalized) { return; }
            _isFinalized = true;

            if (_finallyHandler != null) {
                _finallyHandler();
            }
            _onComplete(this);
        }

    }
}

Add comment to KrewAsync

Add comment to KrewAsync


/**
 * Copyright (c) 2009 Digital Primates IT Consulting Group
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 * @author     Michael Labriola <labriola@digitalprimates.net>
 * @version
 **/
package org.flexunit.internals.runners {
	import flash.utils.getDefinitionByName;
	import flash.utils.getQualifiedClassName;

	import flex.lang.reflect.Klass;
	import flex.lang.reflect.Method;

	import flexunit.framework.Test;
	import flexunit.framework.TestCase;
	import flexunit.framework.TestListener;
	import flexunit.framework.TestResult;
	import flexunit.framework.TestSuite;

	import org.flexunit.runner.Description;
	import org.flexunit.runner.IDescribable;
	import org.flexunit.runner.IDescription;
	import org.flexunit.runner.IRunner;
	import org.flexunit.runner.manipulation.Filter;
	import org.flexunit.runner.manipulation.IFilterable;
	import org.flexunit.runner.notification.IRunNotifier;
	import org.flexunit.runners.model.FrameworkMethod;
	import org.flexunit.token.AsyncTestToken;
	import org.flexunit.token.ChildResult;
	import org.flexunit.utils.ClassNameUtil;

	public class FlexUnit1ClassRunner implements IRunner, IFilterable {

		private var test:Test;
		private var klassOrTest:*;
		private var totalTestCount:int = 0;
		private var numTestsRun:int = 0;
		private var filterRef:Filter = null;

		public function FlexUnit1ClassRunner( klassOrTest:* ) {
			super();

			this.klassOrTest = klassOrTest;

			if ( klassOrTest is Test ) {
				this.test = klassOrTest;
			} else {
				//in this case, we need to make a suite
				this.test = createTestSuiteWithFilter( filterRef );
			}
		}

		protected function describeChild( child:* ):IDescription {
			var method:FrameworkMethod = FrameworkMethod( child );
			return Description.createTestDescription( klassOrTest, method.name, method.metadata?method.metadata[ 0 ]:null );
		}

		private function shouldRun( item:* ):Boolean {
			return filterRef == null || filterRef.shouldRun( describeChild( item ) );
		}

		private function getMethodListFromFilter( klassInfo:Klass, filter:Filter ):Array {
			var list:Array = [];

			for ( var i:int=0; i<klassInfo.methods.length; i++ ) {
				var method:Method = klassInfo.methods[ i ] as Method;
				var frameworkMethod:FrameworkMethod = new FrameworkMethod( method );

				if ( shouldRun( frameworkMethod ) ) {
					list.push( method.name );
				}
			}

			return list;
		}

		private function createTestSuiteWithFilter( filter:Filter = null ):Test {
			if ( !filter ) {
				return new TestSuite( klassOrTest );
			} else {
				var suite:TestSuite = new TestSuite();
				var klassInfo:Klass = new Klass( klassOrTest );
				var methodList:Array = getMethodListFromFilter( klassInfo, filter );

				for ( var i:int=0; i<methodList.length; i++ ) {
					suite.addTest( new klassOrTest( methodList[ i ] ) );
				}
				return suite;
			}
		}

		public static function getClassFromTest( test:Test ):Class {
			var name:String = getQualifiedClassName( test );
			return getDefinitionByName( name ) as Class;
		}

		public function run( notifier:IRunNotifier, previousToken:AsyncTestToken ):void {
			var token:AsyncTestToken = new AsyncTestToken( ClassNameUtil.getLoggerFriendlyClassName( this ) );
			token.parentToken = previousToken;
			token.addNotificationMethod( handleTestComplete );

			var result:TestResult = new TestResult();
			result.addListener( createAdaptingListener( notifier, token ));

			totalTestCount = test.countTestCases();

			test.runWithResult(result);
		}

		protected function handleTestComplete( result:ChildResult ):void {
			if ( ++numTestsRun == totalTestCount ) {
				var error:Error = result.error;
				var token:AsyncTestToken = result.token;

				token.parentToken.sendResult();
			}
		}

		public static function createAdaptingListener( notifier:IRunNotifier, token:AsyncTestToken ):TestListener {
			return new OldTestClassAdaptingListener(notifier, token );
		}

		public function get description():IDescription {
			return makeDescription( test );
		}

		private function makeDescription( test:Test ):IDescription {
			if ( test is TestCase ) {
				var tc:TestCase = TestCase( test );
				//return null;
				return Description.createTestDescription(getClassFromTest( tc ), tc.className );
			} else if ( test is TestSuite ) {
				var ts:TestSuite = TestSuite( test );
				var name:String = ts.className == null ? "" : ts.className;
				var description:IDescription = Description.createSuiteDescription(name);
				var n:int = ts.testCount();
				var tests:Array = ts.getTests();
				for ( var i:int = 0; i < n; i++)
					description.addChild( makeDescription( tests[i] ) );

				return description;
			} else if (test is IDescribable) {
				var adapter:IDescribable = IDescribable( test );
				return adapter.description;
//// not currently supporting this as the old flex unit didn't have it
/* 			} else if (test is TestDecorator) {
				TestDecorator decorator= (TestDecorator) test;
				return makeDescription(decorator.getTest());
 */			} else {
				// This is the best we can do in this case
				return Description.createSuiteDescription( test.className );
			}
		}

		public function filter( filter:Filter ):void {
			if ( test is IFilterable ) {
				var adapter:IFilterable = IFilterable( test );
				adapter.filter(filter);
			}

			this.filterRef = filter;
			test = createTestSuiteWithFilter( filterRef );
		}

/* 		public void sort(Sorter sorter) {
			if (fTest instanceof Sortable) {
				Sortable adapter= (Sortable) fTest;
				adapter.sort(sorter);
			}
		}
 */	}
}
import flexunit.framework.TestListener;
import org.flexunit.runner.notification.RunNotifier;
import flexunit.framework.Test;
import org.flexunit.runner.Description;
import org.flexunit.runner.notification.Failure;
import flexunit.framework.TestCase;
import org.flexunit.runner.IDescribable;
import flexunit.framework.AssertionFailedError;
import org.flexunit.internals.runners.FlexUnit1ClassRunner;
import org.flexunit.token.AsyncTestToken;
import org.flexunit.runner.notification.IRunNotifier;
import org.flexunit.runner.IDescription;

class OldTestClassAdaptingListener implements TestListener {
	private var notifier:IRunNotifier;
	private var token:AsyncTestToken;

	public function OldTestClassAdaptingListener( notifier:IRunNotifier, token:AsyncTestToken ) {
		this.notifier = notifier;
		this.token = token;
	}

	public function endTest( test:Test ):void {
		notifier.fireTestFinished(asDescription(test));
		token.sendResult();
	}

	public function startTest( test:Test ):void {
		notifier.fireTestStarted(asDescription(test));
	}

	// Implement junit.framework.TestListener
	public function addError( test:Test, error:Error ):void {
		var failure:Failure = new Failure(asDescription(test), error );
		notifier.fireTestFailure(failure);
	}

	private function asDescription( test:Test ):IDescription {
		if (test is IDescribable) {
			var facade:IDescribable = test as IDescribable;
			return facade.description;
		}

		return Description.createTestDescription( FlexUnit1ClassRunner.getClassFromTest( test ), getName(test) );
	}

	private function getName( test:Test ):String {
		if ( test is TestCase )
			return TestCase( test ).methodName;
		else
			return test.toString();
	}

	public function addFailure( test : Test, error : AssertionFailedError ) : void {
		addError( test, error );
	}
}

/**
 * Copyright (c) 2009 Digital Primates IT Consulting Group
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 * @author     Michael Labriola <labriola@digitalprimates.net>
 * @version
 **/
package org.flexunit.internals.runners {
	import flash.utils.getDefinitionByName;
	import flash.utils.getQualifiedClassName;

	import flex.lang.reflect.Klass;
	import flex.lang.reflect.Method;

	import flexunit.framework.Test;
	import flexunit.framework.TestCase;
	import flexunit.framework.TestListener;
	import flexunit.framework.TestResult;
	import flexunit.framework.TestSuite;

	import org.flexunit.runner.Description;
	import org.flexunit.runner.IDescribable;
	import org.flexunit.runner.IDescription;
	import org.flexunit.runner.IRunner;
	import org.flexunit.runner.manipulation.Filter;
	import org.flexunit.runner.manipulation.IFilterable;
	import org.flexunit.runner.notification.IRunNotifier;
	import org.flexunit.runners.model.FrameworkMethod;
	import org.flexunit.token.AsyncTestToken;
	import org.flexunit.token.ChildResult;
	import org.flexunit.utils.ClassNameUtil;

	public class FlexUnit1ClassRunner implements IRunner, IFilterable {

		private var test:Test;
		private var klassOrTest:*;
		private var totalTestCount:int = 0;
		private var numTestsRun:int = 0;
		private var filterRef:Filter = null;

		public function FlexUnit1ClassRunner( klassOrTest:* ) {
			super();

			this.klassOrTest = klassOrTest;

			if ( klassOrTest is Test ) {
				this.test = klassOrTest;
			} else {
				//in this case, we need to make a suite
				this.test = createTestSuiteWithFilter( filterRef );
			}
		}

		protected function describeChild( child:* ):IDescription {
			var method:FrameworkMethod = FrameworkMethod( child );
			return Description.createTestDescription( klassOrTest, method.name, method.metadata?method.metadata[ 0 ]:null );
		}

		private function shouldRun( item:* ):Boolean {
			return filterRef == null || filterRef.shouldRun( describeChild( item ) );
		}

		private function getMethodListFromFilter( klassInfo:Klass, filter:Filter ):Array {
			var list:Array = [];

			for ( var i:int=0; i<klassInfo.methods.length; i++ ) {
				var method:Method = klassInfo.methods[ i ] as Method;
				var frameworkMethod:FrameworkMethod = new FrameworkMethod( method );

				if ( shouldRun( frameworkMethod ) ) {
					list.push( method.name );
				}
			}

			return list;
		}

		private function createTestSuiteWithFilter( filter:Filter = null ):Test {
			if ( !filter ) {
				return new TestSuite( klassOrTest );
			} else {
				var suite:TestSuite = new TestSuite();
				var klassInfo:Klass = new Klass( klassOrTest );
				var methodList:Array = getMethodListFromFilter( klassInfo, filter );

				for ( var i:int=0; i<methodList.length; i++ ) {
					var numConstructorArgs:int = klassInfo.constructor.parameterTypes.length;
					var test:Test;

					if ( numConstructorArgs == 0 ) {
						test = klassInfo.constructor.newInstance() as Test;
						if ( test is TestCase ) {
							//If this is a testCase && it does not take a constructor argument
							//then we try to pass it into methodName
							TestCase( test ).methodName = methodList[ i ];
						}
					} else if ( numConstructorArgs == 1 ) {
						test = klassInfo.constructor.newInstance( methodList[ i ] ) as Test;
					} else {
						throw new InitializationError( "Asking to instatiate TestClass with unknown number of arguments" );
					}

					suite.addTest( test );
				}
				return suite;
			}
		}

		public static function getClassFromTest( test:Test ):Class {
			var name:String = getQualifiedClassName( test );
			return getDefinitionByName( name ) as Class;
		}

		public function run( notifier:IRunNotifier, previousToken:AsyncTestToken ):void {
			var token:AsyncTestToken = new AsyncTestToken( ClassNameUtil.getLoggerFriendlyClassName( this ) );
			token.parentToken = previousToken;
			token.addNotificationMethod( handleTestComplete );

			var result:TestResult = new TestResult();
			result.addListener( createAdaptingListener( notifier, token ));

			totalTestCount = test.countTestCases();

			test.runWithResult(result);
		}

		protected function handleTestComplete( result:ChildResult ):void {
			if ( ++numTestsRun == totalTestCount ) {
				var error:Error = result.error;
				var token:AsyncTestToken = result.token;

				token.parentToken.sendResult();
			}
		}

		public static function createAdaptingListener( notifier:IRunNotifier, token:AsyncTestToken ):TestListener {
			return new OldTestClassAdaptingListener(notifier, token );
		}

		public function get description():IDescription {
			return makeDescription( test );
		}

		private function makeDescription( test:Test ):IDescription {
			if ( test is TestCase ) {
				var tc:TestCase = TestCase( test );
				//return null;
				return Description.createTestDescription(getClassFromTest( tc ), tc.className );
			} else if ( test is TestSuite ) {
				var ts:TestSuite = TestSuite( test );
				var name:String = ts.className == null ? "" : ts.className;
				var description:IDescription = Description.createSuiteDescription(name);
				var n:int = ts.testCount();
				var tests:Array = ts.getTests();
				for ( var i:int = 0; i < n; i++)
					description.addChild( makeDescription( tests[i] ) );

				return description;
			} else if (test is IDescribable) {
				var adapter:IDescribable = IDescribable( test );
				return adapter.description;
//// not currently supporting this as the old flex unit didn't have it
/* 			} else if (test is TestDecorator) {
				TestDecorator decorator= (TestDecorator) test;
				return makeDescription(decorator.getTest());
 */			} else {
				// This is the best we can do in this case
				return Description.createSuiteDescription( test.className );
			}
		}

		public function filter( filter:Filter ):void {
			if ( test is IFilterable ) {
				var adapter:IFilterable = IFilterable( test );
				adapter.filter(filter);
			}

			this.filterRef = filter;
			test = createTestSuiteWithFilter( filterRef );
		}

/* 		public void sort(Sorter sorter) {
			if (fTest instanceof Sortable) {
				Sortable adapter= (Sortable) fTest;
				adapter.sort(sorter);
			}
		}
 */	}
}
import flexunit.framework.TestListener;
import org.flexunit.runner.notification.RunNotifier;
import flexunit.framework.Test;
import org.flexunit.runner.Description;
import org.flexunit.runner.notification.Failure;
import flexunit.framework.TestCase;
import org.flexunit.runner.IDescribable;
import flexunit.framework.AssertionFailedError;
import org.flexunit.internals.runners.FlexUnit1ClassRunner;
import org.flexunit.token.AsyncTestToken;
import org.flexunit.runner.notification.IRunNotifier;
import org.flexunit.runner.IDescription;

class OldTestClassAdaptingListener implements TestListener {
	private var notifier:IRunNotifier;
	private var token:AsyncTestToken;

	public function OldTestClassAdaptingListener( notifier:IRunNotifier, token:AsyncTestToken ) {
		this.notifier = notifier;
		this.token = token;
	}

	public function endTest( test:Test ):void {
		notifier.fireTestFinished(asDescription(test));
		token.sendResult();
	}

	public function startTest( test:Test ):void {
		notifier.fireTestStarted(asDescription(test));
	}

	// Implement junit.framework.TestListener
	public function addError( test:Test, error:Error ):void {
		var failure:Failure = new Failure(asDescription(test), error );
		notifier.fireTestFailure(failure);
	}

	private function asDescription( test:Test ):IDescription {
		if (test is IDescribable) {
			var facade:IDescribable = test as IDescribable;
			return facade.description;
		}

		return Description.createTestDescription( FlexUnit1ClassRunner.getClassFromTest( test ), getName(test) );
	}

	private function getName( test:Test ):String {
		if ( test is TestCase )
			return TestCase( test ).methodName;
		else
			return test.toString();
	}

	public function addFailure( test : Test, error : AssertionFailedError ) : void {
		addError( test, error );
	}
}

Fix for JIRA issue http://bugs.adobe.com/jira/browse/FXU-37

git-svn-id: 9278a788294a4258aac04586390fc35fb4a5f335@6909 a9308255-753e-0410-a2e9-80b3fbc4fff6