All Downloads are FREE. Search and download functionalities are using the official Maven repository.

joo.DynamicClassLoader.as Maven / Gradle / Ivy

There is a newer version: 4.1.8
Show newest version
/*
 * Copyright 2009 CoreMedia AG
 *
 * 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.
 */

// JangarooScript runtime support. Author: Frank Wienberg

package joo {
public class DynamicClassLoader extends StandardClassLoader {

  private static function isEmpty(object : Object) : Boolean {
    //noinspection LoopStatementThatDoesntLoopJS
    for (var m:String in object) {
      return false;
    }
    return true;
  }

  public static var INSTANCE:DynamicClassLoader;

  private var resourceByPath : Object = {};
  private var onCompleteCallbacks : Array/**/ = [];

  public function DynamicClassLoader() {
    classLoader = INSTANCE = this;
  }

  /**
   * Keep record of all classes whose dependencies still have to be loaded.
   */
  private var pendingDependencies : Array/**/ = [];
  /**
   * false => pending
   * true => loading
   */
  private var pendingClassState : Object/**/ = {};

  override public function prepare(...params):JooClassDeclaration {
    var cd:JooClassDeclaration = JooClassDeclaration(super.prepare.apply(this, params));
    this.pendingDependencies.push(cd);
    fireDependency(cd.fullClassName);
    return cd;
  }

  public function addDependency(dependency:String):void {
    pendingClassState[dependency] = true;
  }

  public function fireDependency(dependency:String):void {
    if (delete this.pendingClassState[dependency]) {
//      if (this.debug) {
//        trace("prepared class " + dependency + ", removed from pending classes.");
//      }
      if (this.onCompleteCallbacks.length) {
        this.loadPendingDependencies();
        if (isEmpty(this.pendingClassState)) {
          this.doCompleteCallbacks(onCompleteCallbacks);
        }
      }
    }
  }

  override protected function doCompleteCallbacks(onCompleteCallbacks : Array/*Function*/):void {
    this.onCompleteCallbacks = [];
    // "invoke later":
    getQualifiedObject("setTimeout")(function() : void {
      initNativeClasses();
      internalDoCompleteCallbacks(onCompleteCallbacks);
    }, 0);
  }

  private function internalDoCompleteCallbacks(onCompleteCallbacks : Array/*Function*/):void {
    super.doCompleteCallbacks(onCompleteCallbacks);
  }

  // separate factory function to move the anonymous function out of the caller's scope:
  private function createClassLoadErrorHandler(fullClassName:String, url:String):Function {
    return function():void {
      classLoadErrorHandler(fullClassName, url);
    };
  }

  public function classLoadErrorHandler(fullClassName:String, url:String):void {
    trace("[ERROR]", "Jangaroo Runtime: Class "+fullClassName+" not found at URL ["+url+"].");
  }

  /**
   * Import the class given by its fully qualified class name (package plus name).
   * All imports are collected in a hash and can be used in the #complete() callback function.
   * Additionally, the DynamicClassLoader tries to load the class from a URL if it is not present on #complete().
   * @param fullClassName : String the fully qualified class name (package plus name) of the class to load and import.
   */
  public override function import_(fullClassName : String) : void {
    super.import_(fullClassName);
    this.load(fullClassName);
  }

  override public function run(mainClassName : String, ...args):void {
    this.load(mainClassName);
    args.splice(0,0,mainClassName);
    super.run.apply(this,args);
  }

  private function load(fullClassName : String) : void {
    var resourcePathMatch:Array = fullClassName.match(/^resource:(.*)$/);
    if (resourcePathMatch) {
      loadResource(resourcePathMatch[1]);
      return;
    }
    if (!this.getClassDeclaration(fullClassName)) {
      if (this.onCompleteCallbacks.length==0) {
        if (this.pendingClassState[fullClassName]===undefined) {
          // we are not yet in completion phase: just add to pending classes:
          this.pendingClassState[fullClassName] = false;
//          if (this.debug) {
//            trace("added to pending classes: "+fullClassName+".");
//          }
        }
      } else {
        if (this.pendingClassState[fullClassName]!==true) {
          // trigger loading:
          this.pendingClassState[fullClassName] = true;
          var url:String = getRelativeClassUrl(fullClassName);
//          if (this.debug) {
//            trace("triggering to load class " + fullClassName + " from URL " + url + ".");
//          }
          var script:Object = loadScriptAsync(url);
          // script.onerror does not work in IE, but since this feature is for debugging only, we don't mind:
          script.onerror = this.createClassLoadErrorHandler(fullClassName, script['src']);
        }
      }
    }
  }

  private static const RESOURCE_TYPE_STRING:String = "String";
  private static const RESOURCE_TYPE_IMAGE:String = "Image";
  private static const RESOURCE_TYPE_AUDIO:String = "Audio";
  private static const RESOURCE_TYPE_BY_EXTENSION:Object = {
    "txt": RESOURCE_TYPE_STRING,
    "csv": RESOURCE_TYPE_STRING,
    "png": RESOURCE_TYPE_IMAGE,
    "gif": RESOURCE_TYPE_IMAGE,
    "jpg": RESOURCE_TYPE_IMAGE,
    "jpeg": RESOURCE_TYPE_IMAGE,
    "mp3": RESOURCE_TYPE_AUDIO,
    "ogg": RESOURCE_TYPE_AUDIO,
    "wav": RESOURCE_TYPE_AUDIO
  };
  // TODO: map more extensions, also for video etc.
  // TODO: improvement: instead of extensions, we could do a HEAD request to the path and map the Content-Type to media/resource type.

  private function loadResource(path:String):void {
    var resource:Object = resourceByPath[path];
    if (!resource) {
      var dotPos:int = path.lastIndexOf('.');
      var extension:String = path.substring(dotPos + 1);
      var resourceType:String = RESOURCE_TYPE_BY_EXTENSION[extension];
      if (resourceType) {
        if (resourceType === RESOURCE_TYPE_STRING) {
          var xhr:Object = new (getQualifiedObject('XMLHttpRequest'))();
          xhr.open('GET', resolveUrl("joo/classes/" + path));
          xhr.onreadystatechange = function():void {
            if (xhr.readyState === 4) {
              delete xhr.onreadystatechange; // only fire once!
              resourceByPath[path] = xhr.responseText;
              fireDependency("resource:" + path);
            }
          };
          xhr.send(null);
          return;
        }
        var resourceTypeClass:Class = getQualifiedObject(resourceType);
        if (resourceTypeClass) {
          resourceByPath[path] = resource = new (resourceTypeClass)();
          if (resourceType === RESOURCE_TYPE_IMAGE) {
            addDependency("resource:" + path);
            resource.onload = function():void {
              fireDependency("resource:" + path);
            };
            resource.onerror = function(m:*):void {
              trace("[WARN]", "Error while loading resource " + path + ": " + m);
              // however, we do not want dynamic loading to fail completely:
              fireDependency("resource:" + path);
            }
          } else if (resourceType === RESOURCE_TYPE_AUDIO) {
            if (!resource['canPlayType']("audio/" + extension)) {
              // try another MIME type / extension:
              var fallbackExtension:String = findFallback(resource);
              if (!fallbackExtension) {
                return;
              }
              path = path.substring(0, dotPos) + "." + fallbackExtension;
            }
            resource.preload = "auto"; // Embed -> load early, but don't wait for load like with images.
          }
          resource.src = resolveUrl("joo/classes/" + path);
        } else {
          trace("[WARN]", "Resource type " + resourceType + " not supported by client, ignoring resource " + path);
        }
      } else {
        trace("[WARN]", "Ignoring unsupported media type of file " + path);
      }
    }
  }

  private static const AUDIO_FALLBACK_ORDER:Array = ["mp3", "ogg", "wav"];
  private static var AUDIO_FALLBACK_EXTENSION:String = null;
  private static function findFallback(audio:Object):String {
    if (AUDIO_FALLBACK_EXTENSION === null) {
      for (var i:int = 0; i < AUDIO_FALLBACK_ORDER.length; i++) {
        var fallback:String = AUDIO_FALLBACK_ORDER[i];
        if (audio['canPlayType']("audio/" + fallback)) {
          return AUDIO_FALLBACK_EXTENSION = fallback;
        }
      }
      trace("[WARN]", "Could not find any audio extension that this client can play (" + AUDIO_FALLBACK_ORDER.join(",") +
        "), no sound available.");
      AUDIO_FALLBACK_EXTENSION = "";
    }
    return AUDIO_FALLBACK_EXTENSION;
  }

  public function getResource(path:String):Object {
    return resourceByPath[path];
  }

  /**
   * Tell Jangaroo to load and initialize all required classes, then call the given function.
   * The function receives an import hash, which can be used in pure JavaScript in a 'with' statement
   * (Jangaroo does not support 'with', there, you would use import declarations!) like this:
   * 
   * joo.classLoader.import_("com.custom.Foo");
   * joo.classLoader.complete(function(imports){with(imports){
   *   Foo.doSomething("bar");
   * }});
   * 
* @param onCompleteCallback : Function * @return void */ public override function complete(onCompleteCallback : Function = undefined) : void { if (onCompleteCallback || this.onCompleteCallbacks.length==0) { this.onCompleteCallbacks.push(onCompleteCallback || defaultOnCompleteCallback); } this.loadPendingDependencies(); if (isEmpty(this.pendingClassState)) { // no deferred classes, thus no dependency will trigger execution, so do it explicitly: doCompleteCallbacks(onCompleteCallbacks); } else { for (var c:String in this.pendingClassState) { this.load(c); } } } private static function defaultOnCompleteCallback() : void { trace("[INFO]", "Jangaroo Runtime: All classes loaded!"); } private function loadPendingDependencies():void { for (var j:int=0; j




© 2015 - 2025 Weber Informatics LLC | Privacy Policy