Source: wk.js

/*jslint plusplus: true, white: true, indent: 2, maxlen: 90, continue: true, unparam: true */
/*global window */
/**
 *  @module $wk$
 *  @author    Wolfgang Kowarschick
 *  @copyright 2012-2013, Wolfgang Kowarschick
 *  <br/>
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted under the terms of the 
 *  Creative Commons License Attribution-NonCommercial-ShareAlike 3.0 Unported    
 *  (CC BY-NC-SA 3.0: http://creativecommons.org/licenses/by-nc-sa/3.0/). 
 */

////////////////////////////////////////////////////////////////////////////////
// Extensions of the top level object Object
////////////////////////////////////////////////////////////////////////////////

// Generalized version of Object.getOwnPropertyDescriptor
// @see http://wiki.ecmascript.org/doku.php?id=harmony:extended_object_api
if (!Object.getPropertyDescriptor)
{ Object.getPropertyDescriptor = 
  function(p_object, p_property) 
  { "use strict";
    var l_pd = Object.getOwnPropertyDescriptor(p_object, p_property),
        l_proto = Object.getPrototypeOf(p_object);
    while (l_pd === undefined && l_proto !== null) 
    { l_pd = Object.getOwnPropertyDescriptor(l_proto, p_property);
      l_proto = Object.getPrototypeOf(l_proto);
    }
    return l_pd;
  };
}

////////////////////////////////////////////////////////////////////////////////
// The $wk$ object 
////////////////////////////////////////////////////////////////////////////////

/**
 *  The sandbox pattern: When this function is evaluated,
 *  it first loads all modules stated in <code>p_modules</code>
 *  and then calls the callback function passed as last argument:
 *  <pre>function(p_wk, p_env_1, ... p_env_n)
 *       { // code running in a sandbox 
 *       }</pre>
 *  
 *  @see Stoyan Stefanov, JavaScript Patterns. O'Reilly, 2010
 * 
 *  @function
 *  @global
 *  @name $wk$­
 *  
 *  @param {String[]|Array} [p_modules]   
 *    A module name or a list of module names. A module name is a string 
 *    <conde>"p1.p2.p3.M"</code>, consiting of zero ore more package/subpackage
 *    names <code>p_i</code> and the module name <code>M</code> proper.
 *    If <code>M === "*"</code>, all modules within the package are loaded.
 *    If <code>M === "**"</code>, all modules within the package and all
 *    subpackages, subsubpackages etc. are loaded.
 *  @param {Object} [p_environment_i]  
 *    Zero or more environments objects, where the content of the modules are
 *    stored.
 *  @param {Function} [p_sandbox]      
 *    The sandbox, a callback function: 
 *    <code>function(p_wk, p_env_1, ... p_env_n){ ...}</code>
 *    <code>pwk</code> is an instance of <code>$wk$</code>. The environment
 *    object contain the <code>p_modules</code> loaded. If there are more
 *    modules stated than environment objects, the last environment object
 *    contains the surplus modules. If no environment objects are passed,
 *    all modules are stored within <code>p_wk</code>.
 *  @param {String|Array|Object} p_annotations
 *    Annotations that control the behaviour of the code executed within the
 *    sandbox. Plaes note: An annotation object may only be stated, if
 *    <code>p_sandbox</code> has been stated. Otherwise an object is 
 *    interpreted as environment object. 
 */
/*jslint browser: true*/

var $wk$;

(function(p_root_wk)
{ "use strict";

  var i, n,
      l_class_props       = ["classFullName", "classID", "classPath"],
      l_class_prop_values,
      l_prototype,
      l_async,
      l_head              = document.getElementsByTagName("head")[0],
      l_wk_modules        = {},
      l_annotations       = { wk: {}, cls: {}, prop: {}, meth: {}},
      l_wk_annotations    = l_annotations.wk,
      l_uid               = -1,
      l_library_name,
      l_version,
      ANNOTATION_WK       = "wk",
      PACKAGE_INIT_FILE   = "/package.json",
      PACKAGE_LOADING     = 0, PACKAGE_INITIALIZED = 1, PACKAGE_NO_INIT_FILE= 2,
      JS                  = "js", JSON$ = "json", XML = "xml", CSS = "css",
      MODULE_NOT_LOADED   = 0, MODULE_LOADING = 1, MODULE_LOADED = 2,
      EMPTY               = {};

  function f_error_getter()
  { throw new Error("Getter method undefined"); }
  
  function f_error_setter()
  { throw new Error("Setter method undefined"); }
  
  function f_annotation_add(p_type,  p_name, p_anno)
  { Object.defineProperty(l_annotations[p_type], p_name, 
                          { value: p_anno, writable : false }
                         );
  }
  function f_annotations_get(p_type) // TBD: use it
  { return l_annotations[p_type]; }

  f_annotation_add
  (ANNOTATION_WK, "@synchronous", 
   function(p_value) { this["@synchronous"] = p_value; }
  );
  f_annotation_add
  (ANNOTATION_WK, "@asynchronous", 
   function(p_value) { this["@synchronous"] = !p_value; }
  );
  
  /** 
   *  The $root$ object of the application.
   *  
   *  @name $wk$.$root$ 
   */
  
  /** 
   *  The name of the library: WKlib
   *
   *  @member {String} $name$
   *  @readonly
   *  @name $wk$.$name$
   */
  l_library_name = "WKlib";

  /** 
   *  The version of the library: 001.00
   *  
   *  @member {Object} $version$
   *  @readonly
   *  @property {String} $version$.version    The version number.
   *  @property {String} $version$.subversion The subversion number.
   *  @property {String} $version$.name       The version name.
   *  @name $wk$.$version$
   */
  l_version = {version: "001", subversion: "00", name: "v001.00"};

  /**
   *  An object created by calling <code>$new$(Class, [1, 2, 3])</code> 
   *  is identical to an object created with <code>new Class(1, 2, 3)</code>.
   *  
   *  @method
   *  @name $wk$.$new$
   *  
   *  @param {Function} p_constructor A function to be used as constructor.
   *  @param {Array}    p_arguments   An array of arguments to be passed
   *                                  to the constructor
   *  @returns {Object} The newly constructed object.
   *  @see http://stackoverflow.com/questions/1606797/use-of-apply-with-new-operator-is-this-possible
   */
  
  function f_new(p_constructor, p_arguments) 
  { function F_C() 
    { return p_constructor.apply(this, p_arguments); }
    F_C.prototype = p_constructor.prototype;
    return new F_C();
  }
  
  /**
   *  <code>$wk$.WKcAsync</code> is a class the objects of which
   *  serialize asynchronous tasks.
   *  <p>
   *  Each object of this class defines a queue and a pool where
   *  asynchrounous tasks can be put.
   *  </p>
   *  <p>
   *  By means of the method <code>push</code> a asynchronous task is
   *  enqueue. It is executed as soon as all tasks that have been enqueued
   *  earlier have finished. 
   *  </p>
   *  <p>
   *  By means of the method <code>add</code> a named asynchronous task is
   *  added to the task pool. Additionally, the ids of all tasks, the
   *  new task depends on, have to be stated. The task is executed 
   *  as soon as all tasks it depends on have finished.
   *  </p>
   * 
   *  @class
   *  @name $wk$.WKcAysnc
   *  @returns {Object} A new async object.
   */
  function WKcAsync()
  { if (!(this instanceof WKcAsync))
    { return f_new(WKcAsync, arguments); }
  
    var self            = this,
        l_queue         = [],
        l_running_queue = false,
        l_pool          = {};
    
    /**
     *  Enqueues new asynchronous tasks.
     *  That task is executed as soons as all tasks that have been
     *  enqueue before have finished.
     *  
     *  @method
     *  @name $wk$.WKcAysnc#push
     *  
     *  @param {Object}   [p_env] The environment which can be accessed
     *                            via <code>this</code>.
     *  @param {Function} p_doit  The function that performs a task 
     *                            asynchronously
     *  @param {Object[]} p_args  The arguments passed to <code>p_doit</code>
     *                            when executed. Every function for which
     *                            the property <code>NO_CALLBACK</code>
     *                            has either not been set or is not equal to 
     *                            <code>true</code> is considered 
     *                            to be a callback function. 
     *                            <code>p_doit</code> must call at least one
     *                            callback function. If no function has been
     *                            stated, an callback function is created
     *                            automatically and passed as last argument
     *                            to <code>doit</code>. 
     *
     *  @returns {Object}  <code>this</code>
     */
    function f_next_queue()
    { var l_f_next;
    
      if (l_queue.length > 0)
      { l_running_queue = true;
        l_f_next = l_queue.shift();
        l_f_next[1].apply(l_f_next[0], l_f_next[2]);
      }
      else
      { l_running_queue = false; }
    }
    
    this.push =
      function()
      { var l_args      = Array.prototype.slice.call(arguments),
            l_arg       = l_args[0],
            p_env       = (l_arg instanceof Function)?undefined:l_args.shift(),
            p_doit      = l_args.shift(),
            i, n,
            l_args_new  = [],
            l_cb_exists = false;
 
        function f_call()
        { this.apply(p_env, arguments); 
          f_next_queue();  
        }
        
        for (i = 0, n = l_args.length; i < n; i++)
        { l_arg = l_args[i];
          if (l_arg instanceof Function && !(l_arg.NO_CALLBACK))
          { l_cb_exists = true;
            l_args_new.push(f_call.bind(l_arg));
          }
          else
          { l_args_new.push(l_arg); }
        }
        if (!l_cb_exists)
        { l_args_new.push(f_next_queue); }
        l_queue.push([p_env, p_doit, l_args_new]);
        
        if (!l_running_queue)
        { f_next_queue(); }
        
        return self;
      };

    /**
     *  Adds new asynchronous tasks to the task pool.
     *  That task is executed as soons as all tasks it depends on have finished.
     *  
     *  @method
     *  @name $wk$.WKcAysnc#add
     *  
     *  @param {String}    p_id    The id of the task.
     *                             It is not possible to add a second
     *                             task with the same id as an elder task.
     *                             In that case an error is dispatched;
     *  @param {String[]|Array}
     *                     [p_ids] The ids of those tasks, the new task
     *                             depends on.
     *  @param {Object}    [p_env] The environment which can be accessed
     *                             via <code>this</code>.
     *  @param {Function}  p_doit  The function that performs a task 
     *                               asynchronously
     *  @param {Object[]}  p_args  The arguments passed to <code>p_doit</code>
     *                             when executed. Every function for which
     *                             the property <code>NO_CALLBACK</code>
     *                             has either not been set or is not equal to 
     *                             <code>true</code> is considered 
     *                             to be a callback function. 
     *                             <code>p_doit</code> must call at least one
     *                             callback function. If no function has been
     *                             stated, an callback function is created
     *                             automatically and passed as last argument
     *                             to <code>doit</code>. 
     *                               
     *  @returns {Object}  <code>this</code>
     */    
    this.add =
      function()
      { var l_args        = Array.prototype.slice.call(arguments),
            p_id          = l_args[0],
            p_env,
            p_doit,
            i, n,
            state         = 0,
            l_arg,
            l_args_new    = [],
            l_task        = { state: 0, waiting: [] },
            l_task_pre,
            l_task_pre_nr = 1,
            l_cb_exists   = false;
      
        if (l_pool[p_id])
        { throw new Error("WKcAsync: Task "+p_id+" does already exist!"); }
        l_pool[p_id] = l_task;
        
        function f_next()
        { var l_w = l_task.waiting, n = l_w.length;
           l_task.state = 1;
          while (n>0)
          { (l_w.shift())(); }
        }
        function f_call()
        { this.apply(p_env, arguments); 
          f_next();
        }
        function f_doit()
        { l_task_pre_nr--;
          if (l_task_pre_nr === 0)
          { p_doit.apply(p_env, l_args_new); } 
        }
      
        for (i = 1, n = l_args.length; i < n; i++)
        { l_arg = l_args[i];
          if (state === 0)
          { if (l_arg.constructor === String)
            { l_task_pre = l_pool[l_arg];
              if (!l_task_pre)
              { throw new Error("WKcAsync: Task "+l_arg+" doesn't exist!"); }
              if ( l_task_pre.state === 0)
              { l_task_pre.waiting.push(f_doit);
                l_task_pre_nr++;  
              }
              continue; 
            }
            else
            { state = 1; }
          }
          if (state === 1)
          { if (l_arg instanceof Function)
            { p_doit = l_arg; }
            else
            { p_env = l_arg;
              p_doit = l_args[++i]; 
            }
            state = 2;
          }
          else // state === 2
          { if (l_arg instanceof Function && !(l_arg.NO_CALLBACK))
            { l_cb_exists = true;
              l_args_new.push(f_call.bind(p_env));
            }
            else
            { l_args_new.push(l_arg); }
          }
        }
        if (!l_cb_exists)
        { l_args_new.push(f_next); }
       
        f_doit();
        
        return self;
      };
   
  }
  
  WKcAsync.noCalllback =
    function(p_function)
    { p_function.NO_CALLBACK = true;
      return p_function;
    };
    
  l_prototype             = WKcAsync.prototype;
  l_prototype.constructor = WKcAsync;
  l_prototype.noCalllback = WKcAsync.noCalllback; 
  l_class_prop_values     = ["$wk$.WKcAsync", "WKcAsync", "$wk$"];
  
  WKcAsync.fullName = l_class_prop_values[0];
  WKcAsync.id       = l_class_prop_values[1];
  WKcAsync.path     = l_class_prop_values[2];
  
  for (i=0, n=l_class_props.length; i < n; i++)
  { Object.defineProperty(l_prototype,
                          l_class_props[i],
                          { configurable: false,
                            enumerable:   false,
                            value:        l_class_prop_values[i],
                            writable:     false
                          }
                         );
  }

  l_async = new WKcAsync();

  /** 
   *  <code>$C$</code> is an object that contains constant values.
   * 
   *  @method
   *  @name $wk$.$C$
   *  
   *  @param {String}  p_name   The name of the constant to be stored.
   *  @param {Object}  p_value  The value of the constant to be stored.
   *  @param {Boolean} [p_getter = false]
   *                            If <code>p_value</code> contains a 
   *                            (parameterless) function and 
   *                            <code>p_getter===true</code> then for
   *                            <code>p_name</code> a getter method is
   *                            stored that calls that function within
   *                            the scope of <code>$C$</code>.
   */
  function $C$(p_name, p_value, p_getter)
  { Object.defineProperty($C$, p_name,
                          p_getter && p_value instanceof Function
                           ? { configurable: false,
                               enumerable:   true,
                               get:          function() 
                                             { return p_value.call($C$); },
                               set:          f_error_setter
                             } 
                           : { configurable: false,
                               enumerable:   true,
                               value:        p_value || null,
                               writable:     false
                             }
                          );
  }

  /** 
   *  <code>$E$</code> is an object that contains event types.
   * 
   *  @method
   *  @name $wk$.$E$
   *  
   *  @param {String} p_event_type  
   *                  The event type to be stored in <code>$E$</code>.
   */
  function $E$(p_event_type)
  { l_uid--;
    Object.defineProperty($E$, p_event_type,
                          { configurable: false,
                            enumerable:   true,
                            value:        l_uid--,
                            writable:     false
                          }
                         );
  }
  
  /**
   *  Creates a chain of nested objects.
   *  <p>
   *  For instance: The following function call
   *  <pre>  $wk$.pkg("ldvcs.view");</pre>
   *  results in the creation of two objects (if they do not already exist):
   *  <pre>
   *   ldvcs
   *   ldvcs.view</pre>
   *  </p>
   *  
   *  @method
   *  @name $wk$.pkg
   *  
   *  @param {String} p_name  A package name, i.e. one or more name literals
   *                          separated by dots.
   *  @param {String} [p_create = true]   
   *                          Indicates, whether or not to create the package,
   *                          if it does not exists
   *  @returns {Object|null}  The last package object of the package 
   *                          object chain or <code>null</code>
   *                          if the package does not exist.
   */
  function f_pkg(p_name, p_create)
  { p_create = 
      (p_create === undefined || p_create === null) ? true : p_create;

    var l_subpackages        = p_name.split("."),    // all literal names
        l_root_name          = l_subpackages[0],
        l_name,
        l_subpackage_object,
        l_modules            = l_wk_modules[l_root_name] || p_root_wk,
        l_module_new,
        l_root_package       = l_modules["*p"] || p_root_wk,
        l_package_object     = l_root_package.root || p_root_wk,
        i, n; 
    
    if (!p_create && !l_modules)
    { return false; }
    
    if (p_name === "")
    { return p_root_wk; }
       
    for (i = 0, n = l_subpackages.length; i < n; i++)   // for each literal name
    { l_name   = l_subpackages[i]; // the current literal name
      // Fetch the associated package object.
      l_subpackage_object = l_package_object[l_name];
    
      // If it does not exist: 
      //   create it or, if p_create === false, return false.
      if (!l_subpackage_object)
      { if (p_create)
        { l_package_object[l_name] = l_subpackage_object = {}; 
          if (i>0)
          { l_module_new = l_modules[l_name];
            if (!l_module_new)
            { l_modules[l_name] = l_module_new = {}; }
            l_modules = l_module_new;
          }
        }
        else
        { return false; }
      }
      // Set the current subpackage object to be the new package object,
      // for which the next subpackage object hast to be created.
      l_package_object = l_subpackage_object;
    } 
    return l_package_object;
  }
  
  /**
   *  An extended version of <code>Object.getOwnPropertyDescriptor</code>.
   *  @method
   *  @name $wk$.getOwnPropertyDescriptor
   *  
   *  @param {String} p_object    The object a property descriptor of which 
   *                              is either to be read or to be defined.
   *  @param {String} p_property  The property of <code>p_object</code>
   *                              which is to be accessed. 

   *  @returns {Object}  The property descriptor of 
   *                     <code>p_object.p_property</code>, if defined.
   */

  function f_get_own_property_descriptor(p_object, p_property)
  { var l_pd_wk,
        l_pd = Object.getOwnPropertyDescriptor(p_object, p_property);
  
    if (l_pd && p_object["$*!"] && p_object["$*!"])
    { l_pd_wk = p_object["$*!"][p_property];
      if (l_pd_wk && l_pd_wk.overridable)
      { l_pd.overridable = l_pd_wk.overridable; }    
    }
    return l_pd;
  }
  
  /**
   *  An extended version of <code>Object.getPropertyDescriptor</code>.
   *  @method
   *  @name $wk$.getPropertyDescriptor
   *  
   *  @param {String} p_object    The object a property descriptor of which 
   *                              is either to be read or to be defined.
   *  @param {String} p_property  The property of <code>p_object</code>
   *                              which is to be accessed. 

   *  @returns {Object}  The property descriptor of 
   *                     <code>p_object.p_property</code>, if defined.
   */
  function f_get_property_descriptor(p_object, p_property) 
  { var l_pd    = f_get_own_property_descriptor(p_object, p_property),
        l_proto = Object.getPrototypeOf(p_object);
    while (l_pd === undefined && l_proto !== null) 
    { l_pd = f_get_own_property_descriptor(l_proto, p_property);
      l_proto = Object.getPrototypeOf(l_proto);
    }
    return l_pd;
  }
  
  /**
   *  An extended version of <code>Object.defineProperty</code>.
   *  <p>
   *  The property descriptor set via <code>p_descriptor</code> or returned 
   *  by <code>$wk$.setProperty</code> may contain several attributes:
   *  <dl>
   *    <dt><code>configurable</code> 
   *        (<code>boolean</code>, default: <code>false</code>)</dt>
   *    <dd>see <code>Object.defineProperty</code> or 
   *        <code>Object.getOwnPropertyDescriptor</code></dd>
   *    <dt><code>enumerable</code> 
   *        (<code>boolean</code>, default: <code>false</code>)</dt>
   *    <dd>see <code>Object.defineProperty</code> or 
   *        <code>Object.getOwnPropertyDescriptor</code></dd>
   *    <dt><code>value</code> 
   *        (any JavaScript value, default: <code>undefined</code>)</dt>
   *    <dd>see <code>Object.defineProperty</code> or 
   *        <code>Object.getOwnPropertyDescriptor</code></dd>
   *    <dt><code>writable</code> 
   *        (<code>boolean</code>, default: <code>true</code>)</dt>
   *    <dd>see <code>Object.defineProperty</code> or 
   *        <code>Object.getOwnPropertyDescriptor</code></dd>
   *    <dt><code>get</code> 
   *        (<code>Function</code>, default: <code>undefined</code>)</dt>
   *    <dd>see <code>Object.defineProperty</code> or 
   *        <code>Object.getOwnPropertyDescriptor</code></dd>
   *    <dt><code>set</code> 
   *        (<code>Function</code>, default: <code>undefined</code>)</dt>
   *    <dd>see <code>Object.defineProperty</code> or 
   *        <code>Object.getOwnPropertyDescriptor</code></dd>
   *    <dt><code>overridable</code> 
   *        (<code>boolean</code>, default: <code>false</code>)</dt>
   *    <dd>Indicates whether the property value may be overriden by
   *        <code>$wk$.mixin</code></dd>
   *  </dl>
   *  If a setter or getter function has been defined but not both,
   *  then an error is thrown whenever the undefined method is tried
   *  to be invoked.
   *  </p>
   *  @method
   *  @name $wk$.setProperty
   *  
   *  @param {String} p_object      The object a property descriptor of which 
   *                                is either to be read or to be defined.
   *  @param {String} p_property    The property of <code>p_object</code>
   *                                which is to be accessed. 
   *  @param {Object} p_descriptor  The descriptor to be asigned to or to be
   *                                mixed into <code>p_object.p_property</code>
   *  @param {Object} [p_mixin = false] 
   *                                If <code>false</code> an existing desctiptor
   *                                is overridden, otherwise the new descriptor
   *                                is mixed into the old one.
   *                              
   *  @returns {Object}  <code>this</code>
   */
  function f_set_property (p_object, p_property, p_descriptor, p_mixin)
  { var l_default = p_mixin 
                    ? f_get_own_property_descriptor(p_object, p_property)
                      || EMPTY
                    : EMPTY,
        l_pdesc =   { configurable: l_default.configurable    ||
                                    p_descriptor.configurable || false,
                      enumerable:   l_default.configurable    ||
                                    p_descriptor.enumerable   || true
                    }; 
    
    if (l_default.get === f_error_getter)
    { delete l_default.get; }
    if (l_default.set === f_error_setter)
    { delete l_default.set; }
                       
    if (p_descriptor.value)
    { l_pdesc.value    = l_default.value    || p_descriptor.value;
      l_pdesc.writable = l_default.writable || p_descriptor.writable || true;
    }
    else if (p_descriptor.get)
    { l_pdesc.get = l_default.get || p_descriptor.get;
      l_pdesc.set = l_default.set || p_descriptor.set || f_error_setter;
    }
    else if (p_descriptor.set)
    { l_pdesc.get = l_default.get || f_error_getter;
      l_pdesc.set = l_default.set || p_descriptor.set;
    }
    else
    { l_pdesc.value    = l_default.value    || undefined;
      l_pdesc.writable = l_default.writable || p_descriptor.writable || true;         
    }
         
    Object.defineProperty(p_object, p_property, l_pdesc);
        
    if (p_descriptor.overridable)
    { l_pdesc = p_object["$*!"];
      
      if (!l_pdesc)
      { Object.defineProperty(p_object, "$*!",
                              { enumerable:   false,
                                configurable: false,
                                writable:     false,
                                value:        {}
                              }
                             );
        l_pdesc = p_object["$*!"];
      }
        
      if (!l_pdesc[p_property])
      { l_pdesc[p_property] = { overridable: p_descriptor.overridable }; }
      else
      { l_pdesc[p_property].overridable = p_descriptor.overridable; }
    }
  }
  
  /**
   *  Recursively mixes properties of a source object into a target object.
   *  Overridable properties are overridden, others not.  
   *  
   *  @method
   *  @name $wk$.mixin
   *  
   *  @param {Object|String} p_target An arbitray object.
   *  @param {Object|String} p_source 
   *         Another arbitray object (or a path name of an object) 
   *         the properties of which are to be recursively mixed into 
   *         <code>p_target</code>.
   *  @param {Function} p_filter
   *         A function code>function(p_source [,p_target])</code> 
   *         that filters <code>p_source</code> before mixing it into 
   *         <code>p_target</code>. 
   */
  function f_mixin(p_source, p_target, p_filter)
  { var l_key = null, 
        l_desc_target, 
        l_desc_source,
        l_target,
        l_value;
  
    p_source = (p_source && p_source.constructor === String) 
                 ? f_pkg(p_source, false) 
                 : p_source;
    p_target = (p_target.constructor === String)
                 ? f_pkg(p_target, false)
                 : p_target;
    if (p_filter) 
    { p_source = p_filter(p_source, p_target); }
    
    if (p_source === p_target)
    { return; }
    
    for (l_key in p_source)
    { if (p_source.hasOwnProperty(l_key))
      { if (p_target.hasOwnProperty(l_key))
        { l_desc_target = f_get_own_property_descriptor(p_target, l_key);
          if (    (l_desc_target.get || l_desc_target.set)
               && !(l_desc_target.get && l_desc_target.set)
              )
          { l_desc_source = f_get_own_property_descriptor(p_source, l_key);
            if (!l_desc_target.get)
            { l_desc_target.get = l_desc_source.get; }
            if (!l_desc_target.set)
            { l_desc_target.set = l_desc_source.set; }
            f_set_property(p_target, l_key, l_desc_target, true);
           }
          else 
          { l_target = p_target[l_key];
            if (l_target instanceof Object && !(l_target instanceof Function))
            { f_mixin(p_source[l_key], l_target); }
          }
        }
        else
        { l_desc_source = f_get_own_property_descriptor(p_source, l_key);
          l_value = l_desc_source.value;
          if (l_value instanceof Function && l_value.$original$)
          { l_desc_source.value = l_value.$original$; }
          else 
          { if (   (l_desc_source.get || l_desc_source.set)
                && !(l_desc_source.get && l_desc_source.set)
               )
            { l_desc_target =  
                Object.getPropertyDescriptor(Object.getPrototypeOf(p_target),
                                             l_key
                                            ); //TBD
              if (l_desc_target)
              { if (!l_desc_source.get)
                { l_desc_source.get = l_desc_target.get; }
                if (!l_desc_source.set)
                { l_desc_source.set = l_desc_target.set; }
              }
            }
          }
          Object.defineProperty(p_target, l_key, l_desc_source); //TBD
        }
      }
    }
  }

  /**
   *  Creates root packages.
   *  
   *  @method
   *  @name $wk$.rootPackage
   *  
   *  @param {Object} p_roots  
   *         An object containing one or many descriptors of root packages.
   *  @param {Object} p_roots.NAME
   *         Each attribute of <code>p_root</code> describes the root package 
   *         named <code>NAME</code>.
   *  @param {Object} [p_roots.NAME.root = <system root object>]
   *         The top level object where the package modules 
   *         can be found.
   *  @param {Object} [p_roots.NAME.url]
   *         The top level URL where the source files of the package modules 
   *         can be found. If no url is passed, the modules of that
   *         package cannot be loaded but must be created directly.
   *  @param {Function} [p_roots.NAME.packagePath = ppath]
   *         A function that maps a package name to a path name that is appended
   *         to <code>p_root.NAME.url</code>. The default function 
   *         <code>ppath</code> deletes all <code>$</code>-symbols from
   *         the name, replaces dots (<code>"."</code>) by slashes
   *         (<code>"/"</code>).                   
   *  @param {String} [p_roots.NAME.packageIndexFile = "index.json"]
   *         The name of the json file, where the list of al
   *  @param {Function} [p_roots.NAME.moduleFile = mfile]
   *         A function <code>f(p_name, p_type)</code> that maps a module name 
   *         to a file name. The default function <code>mfile</code> 
   *         applies <code>ppath</code> (see above) to <code>p_name</code>
   *         and then adds <code>"." + p_type</code> to the path name.                   
   */
  function f_root_pkg(p_roots)
  { var l_root_name = null,
        l_root,
        l_root_2,
        l_root_default,
        l_module_new;
  
    function f_ppath(p_name)
    { return p_name.replace(/\$/g, "").replace(/\./g, "/"); }

    function f_mfile(p_name, p_extension)
    { return f_ppath(p_name) + "." + p_extension; }

    l_root_default 
    = { packagePath: f_ppath,
        moduleFile:  f_mfile,
        loaders:
        { // Should I try the following solution?       
          // http://stackoverflow.com/questions/7718935/load-scripts-asynchronously
          js: 
            function(p_wk, p_callback)
            { // TBD: node.js
              var l_script = document.createElement("script");
              l_script.type    = "text/javascript";
              l_script.defer   = true;
//            l_script.async   = false;
              l_script.src     = this.url;
              l_script.onload  = p_callback();
              l_script.onerror = 
                function()
                { throw new Error(l_script.src + " not found"); }; 
              l_head.appendChild(l_script);
            },
                      
          json:
            function(p_wk, p_callback)
            { var l_xhr     = new XMLHttpRequest(),
                  self      = this,
                  l_module  = this.module,
                  l_filter  = this.filter,
                  l_sync    = p_wk["@synchronous"],
                  f_onload  = function() 
                              { var l_json = JSON.parse(this.response);
                                if (!l_module && !l_filter)
                                { self.module = l_json; }
                                else 
                                { if (!l_module)
                                  { l_module = self.module = {}; }
                                  f_mixin(l_json, l_module, l_filter);
                                }
                   
                                p_callback();
                              },
                  f_onerror = function()
                              { throw new Error(l_module.url + " not found"); };
 
              l_xhr.overrideMimeType("application/json");  
              l_xhr.open("GET", this.url, !l_sync);
              
              if (!l_sync)
              { l_xhr.onload  = f_onload;
                l_xhr.onerror = f_onerror;
              }
              
              l_xhr.send();
              
              if (l_sync)
              { if (l_xhr.status === 200) 
                { f_onload.call({ response: l_xhr.responseText}); }
                else
                { f_onerror(); }
              }
            },

          css:
            function(p_module, p_callback)
            { throw new Error("The CSS loader has not been implemented jet!"); },

          xml:
            function(p_module, p_callback)
            { throw new Error("The CSS loader has not been implemented jet!"); }
        }
     };
  
    for (l_root_name in p_roots)
    { if (p_roots.hasOwnProperty(l_root_name))
      { l_root = p_roots[l_root_name];
        f_mixin(l_root_default, l_root);
        //l_root.url  = (l_root.url || "");
        if (l_root.root)
        { l_root_2    = (l_root.root)[l_root_name];
          if (!l_root_2)
          { (l_root.root)[l_root_name] = { "$root$" : l_root.root }; }
          else
          { l_root_2.$root$ = l_root_2.$root$ || l_root.root; }   
        }
        else
        { l_root.root = {}; }
        l_module_new = l_wk_modules[l_root_name]  = { "*p": l_root };
        /*--*/Object.defineProperty(l_module_new, "*p", { enumerable : false });
      }
    }
  }

  /**
   *  Adds one ore more modules to the module list of <code>$wk$</code>.
   *  Those modules can be loaded and added to 
   *  <code>$wk$</code> sandbox environments lateron.
   * 
   *  @method
   *  @name $wk$.moduleAdd
   *  
   *  @param {Array}    arguments         
   *                      Default parameter
   *  @param {String}   arguments[i].name  
   *                      The unique name of the module (including the
   *                      name of the module package).
   *  @param {String}   [arguments[i].type] 
   *                      The type of the module (<code>$wk$.MODULE_TYPE</code>):
   *                      <code>js</code>, <code>css</code>, <code>json</code>,
   *                      <code>xml</code>, ...
   *  @param {String}   [arguments[i].url]       
   *                      The URL, where the definition of the module can be 
   *                      found. Usually computed automatically
   *                      (determined by the package root, package name ...)
   *  @param {Function} [arguments[i].loader]    
   *                      A function <code>function(p_callback)</code> 
   *                      to load the module (<code>this</code>) 
   *                      from <code>url</code>. Finally the
   *                      callback function must be invoked.
   *                      For <code>js</code>, <code>css</code>, 
   *                      <code>json</code> and <code>xml</code> 
   *                      there exists default loaders. (TBD: css, xml)
   *  @param {Object}   [arguments[i].module]
   *                      The module itself. If it is not stated explicitly, it
   *                      is loaded, when the module is accessed for the 
   *                      first time.
   *  @param {Function} [arguments[i].init]      
   *                      A function <code>function(p_callback)</code> 
   *                      that is called, when the module has been loaded.
   *                      The callback function passed <strong>must</strong>
   *                      be called, when the module has been initialized.
   *  @param {Array}    [arguments[i].required]  
   *                      A list of module that must be loaded before the 
   *                      <code>p_module</code> can be loaded.
   */
  function f_module_add()
  { var i, j, n,
        l_source,
        l_name,
        l_name_array,
        l_length,
        l_modules, l_module_new,
        l_type,
        l_root_package,
        l_loaders,
        l_package_name = "",
        l_package_url  = "",
        l_target,
        l_callbacks,
        f_mfile,
        l_key = null;
  
    for (i = 0, n = arguments.length; i < n; i++) 
    { l_source      = arguments[i];
      l_name_array  = l_source.name.split(".");
      l_length      = l_name_array.length-1;
      l_modules     = l_wk_modules;
      if (l_length !== 0)
      { l_name    = l_name_array[0];
        l_modules = l_modules[l_name];
        if (!l_modules)
        { throw new Error("Root package " + l_name + " does not exist!");}
        l_root_package = l_modules["*p"];
        f_mfile   = l_root_package.moduleFile;
        l_loaders = l_root_package.loaders;
        l_package_name = l_name;
        l_package_url  = l_modules["*p"].url;
      }
      for (j = 1; j < l_length; j++)
      { l_name = l_name_array[j];
        l_module_new = l_modules[l_name];
        l_package_name += "." + l_name;
        
        if (!l_module_new)
        { l_modules[l_name] = l_module_new = { "*p": { name: l_package_name } };
          Object.defineProperty(l_module_new, "*p", { enumerable : false });
        }
        l_modules = l_module_new;
      }
      
      l_name                 = l_name_array[l_length];
      l_target               = l_modules[l_name];
      l_source.required      = l_source.required || [];
      l_type = l_source.type = l_source.type     || JS;
      
      if (l_length !== 0)
      { l_source.url =    l_source.url                       
                       || f_mfile(l_package_name + "." + l_name, l_type);
        
        if (l_package_url)
        { l_source.url = l_package_url + "/" + l_source.url; }

        l_source.loader = l_source.loader || l_loaders[l_type];
        if (!l_source.loader)
        { throw new Error( "Loader for module '" + l_name + " is missing!");}
      }

      if (!l_target)
      { l_modules[l_name] = l_target = l_source; }
      else
      { for (l_key in l_source)
        { if (l_source.hasOwnProperty(l_key))
          { l_target[l_key] = l_source[l_key]; }
        }
      }

      l_target.state = l_target.module ? MODULE_LOADED : MODULE_NOT_LOADED;
      
      if (l_target.module)
      { l_callbacks = l_target.$cb$;
        if (l_callbacks)
        { while (l_callbacks.length > 0)
          { (l_callbacks.shift())(); }
        }        
      }
    }
  }

  /**
   *  Initializes a root package (which must have been created by
   *  <code>$wk$.rootPackage</code> before) by adding information about 
   *  <strong>all</strong> modules and subpackages that are 
   *  directly or indirectly members of that package.
   *
   *  TBD: Description of the syntax of <code>p_init</code>.
   *  
   *  @method
   *  @name $wk$.rootPackageInit
   *  
   *  @param {String} p_root_name  
   *    The name of the root package.
   *  @param {JSON}   [p_init]  
   *    The initialization data describing the modules to be added.
   *    <code>p_init</code> is an object each property of which describes
   *    a module or a subpackage that is to be loaded. If no initialization
   *    object is stated, it is tried to load the JSON file 
   *    <code>package.json</code> from the URL where the root
   *    package is stored. If this file exists, its content is used
   *    to initialize the root package.   
   */
  function f_root_pkg_init_aux(p_path, p_init)
  { var l_key = null, l_prop, l_type;
    for (l_key in p_init)
    { if (p_init.hasOwnProperty(l_key))
      { l_prop = p_init[l_key];
        l_type = l_prop.type;
        if (    l_type // i.e. l_prop describes a module but no package
             || (!l_type && l_key.match(/^[A-Z]/))
           ) 
        { l_prop.name = l_prop.name || p_path + l_key;
          f_module_add(l_prop);
        }
        else
        { f_root_pkg_init_aux(p_path + l_key + ".", l_prop); }
      }
    }
  }
  
  function f_root_pkg_init(p_root_name, p_init)
  { var l_root, l_root_pkg_desc, l_root_pkg_url, l_xhr;

    if (p_init)
    { f_root_pkg_init_aux(p_root_name + ".", p_init); }
    else
    { l_root = l_wk_modules[p_root_name];
      if (l_root)
      { l_root_pkg_desc = l_root["*p"];
        if (   l_root_pkg_desc 
            && !(l_root_pkg_desc.state)
           )
        { l_async
            .push(function(p_callback)
                  { l_root_pkg_desc.state = PACKAGE_LOADING;
                    l_root_pkg_url = l_root_pkg_desc.url;
                    if (l_root_pkg_url !== undefined)
                    { l_xhr = new XMLHttpRequest();
                      if (l_root_pkg_url)
                      { l_root_pkg_url += "/"; }
                        l_xhr.open("GET", 
                                     l_root_pkg_url  
                                   + l_root_pkg_desc.packagePath(p_root_name) 
                                   + PACKAGE_INIT_FILE,
                                   false // TBD????: true
                                  );
                        l_xhr.send();
                        if (l_xhr.status === 200) 
                        { f_root_pkg_init(p_root_name, 
                                          JSON.parse(l_xhr.responseText)
                                         );
                          l_root_pkg_desc.state = PACKAGE_INITIALIZED;
                        }
                        else
                        { l_root_pkg_desc.state = PACKAGE_NO_INIT_FILE; }
                     }
                     p_callback();
                   }
                 );
        }
      } 
    }
  }

  //////////////////////////////////////////////////////////////////////////////
  // Definition of $wk$
  //////////////////////////////////////////////////////////////////////////////

  $wk$ = 
  function WK()
  { // Ensure that the function is called as a constructor.
    if (!(this instanceof WK))
    { return f_new(WK, arguments); }
    
    var self                  = this,
        p_modules             = [], 
        p_environments        = [self],
        l_environment         = self,
        l_environments        = [self],
        p_sandbox             = null,
        p_annotations         = {},
        l_arg,
        l_arg_state           = 0,
        i, j, n, l_key        = null, 
        l_length,
        l_package_name        = null,
        l_module_name         = null,
        l_module_fullname     = null,
        l_module_name_split   = null,
        l_modules             = [],
        l_module,
        l_modules_waiting_for = 1,
        l_next_env            = true;

    function f_annotation(p_name, p_value)
    { var l_anno_fkt = l_wk_annotations[p_name];
      if (!l_anno_fkt)
      { throw new Error("$wk$: The annotation " + p_name + " is undefined"); }
      l_anno_fkt.call(self, p_value); 
    }

    for (i = 0, n = arguments.length; i < n; i++)
    { l_arg = arguments[i];
      if (l_arg_state === 0) // Modules (optional)
      { if (l_arg instanceof Array)
        { l_arg_state = 1; 
          p_modules = l_arg; 
          continue; 
        }
        else if (l_arg.constructor === String)
        { if (l_arg.charAt(0) === "@")
          { l_arg_state = 2; }
          else
          { p_modules.push(l_arg); }
          continue; 
        }
        else
        { l_arg_state = 1; }
      } 
      if (l_arg_state === 1) // Sandbox (optional)
      { if (l_arg instanceof Function)
        { p_sandbox   = l_arg;
          l_arg_state = 2;
        }
        else
        { if (l_arg instanceof Array|| l_arg.constructor === String)
          { l_arg_state = 2;}
          else
          { p_environments.push(l_arg); continue; } 
        }
      }
      if (l_arg_state === 2)
      { if (l_arg instanceof Array)
        { p_annotations[l_arg[0]] = l_arg[1]; }
        else if (l_arg.constructor === String)
        { p_annotations[l_arg] = true; }
        else if (l_arg instanceof Object)
        { for (l_key in l_arg)
          { if (l_arg.hasOwnProperty(l_key))
            { p_annotations[l_key] = l_arg[l_key]; }
          }
        }
      }       
    }
    
    /*jslint forin: true*/
    for (l_key in p_annotations)
    { f_annotation(l_key, p_annotations[l_key]); }
    
    if (p_environments.length > 1)
    { l_environments = p_environments.slice(0);
      l_environments.shift(); 
    }
    
    if (p_sandbox === undefined)
    { throw new Error("new $wk$: Sandbox is missing!"); }

    this.$wk$ = 
      function() 
      { var l_proto = WK.prototype,
            l_result;
        WK.prototype = self;
        l_result = f_new(WK, arguments);
        WK.prototype = l_proto;
        return l_result;
      };

    function f_module_find(p_module_name)
    { var i, n,
          l_module_name_split = p_module_name.split("."),
          l_module            = l_wk_modules,
          l_name              = "";
    
      for (i = 0, n = l_module_name_split.length; i < n; i++)
      { l_name = l_module_name_split[i];
    
        if (!l_module[l_name])
        { if (i===0 && f_pkg(p_module_name, false))
          { l_module = { module: f_pkg(p_module_name),
                         state:  MODULE_LOADED
                       };
            break;
          }
          else
          { throw new Error("moduleLoad: Module "+l_name+" does not exist."); }
        }
        else
        { l_module = l_module[l_name]; }
      }
      
      return l_module;
    }
    
    function f_module_fetch(p_module, p_callback)
    { function f_callback()
      { var l_callbacks;
        if (p_module.type !== JS)
        { p_module.state = MODULE_LOADED;
          l_callbacks = p_module.$cb$;
          if (l_callbacks)
          { while (l_callbacks.length > 0)
            { (l_callbacks.shift())(); }
          }
        }
        p_callback();
      }
      
      if (p_module.state === MODULE_LOADED)
      { if (p_module.init)
        { p_module.init(p_callback);
          delete p_module.init;
        }
        else
        { p_callback(); }
      }
      else if (p_module.state === MODULE_LOADING)
      // As the module is alereday being load, nothing has to be done.
      { p_callback(); }
      else
      // Load and/or initialize the module.
      { if (!p_module.url && !p_module.init)
        { throw new Error(  "URL or 'init' is missing for module '"
                          + p_module.name
                          + "'"
                         );
        }
        
        p_module.state = MODULE_LOADING;
        if (p_module.url)
        { if (p_module.init)
          { p_module.loader(self, 
                            function(){p_module.init(f_callback);
                                       delete p_module.init;
                                      }
                           ); 
          }
          else
          { p_module.loader(self, f_callback); }
        }
        else
        { p_module.init(f_callback);
          delete p_module.init;
        }
      }
    }
    
    function f_star_begin()
    { if (l_environments.length > 0) 
      { l_environment = l_environments.shift(); }
      l_next_env = false;
    }

    function f_star_end()
    { l_next_env = true; }

    function f_sandbox()
    { var l_module; 
      //self.$trace$ = (self.$trace$ || "") + l_modules_waiting_for + ", ";
      if (l_modules_waiting_for === 0)
      { while (l_modules.length > 0)
        { l_module = l_modules.shift();
          if (l_module instanceof Function) // ===f_star_begin || ===f_star_end
          { l_module(); }
          else
          { if (l_next_env && l_environments.length > 0)
            { l_environment = l_environments.shift(); }
            f_mixin(l_module.module, l_environment);            
          }
        }
        if (p_sandbox !== null)
        { p_sandbox.apply(self, p_environments); }
      }
    }
    
    function f_observer_add(p_module)
    { function f_callback()
      { l_modules_waiting_for--; f_sandbox(); }

      l_modules.push(p_module);
      if (p_module.state !== MODULE_LOADED)
      { l_modules_waiting_for++;
        if (!p_module.$cb$)
        { p_module.$cb$ = []; }
        p_module.$cb$.push(f_callback);
      }
      else
      { f_sandbox(); }
    }
    
    function f_module_add_deep(p_package, p_deep)
    { var l_module_or_package, l_key = null;
    
      /*jslint forin: true*/
      for (l_key in p_package)
      { l_module_or_package = p_package[l_key];
        if (l_module_or_package.url || l_module_or_package.module)
        { f_observer_add(l_module_or_package);
          l_async.push(f_module_fetch, l_module_or_package); 
        }
        else if (p_deep && l_module_or_package["*p"])
        { f_module_add_deep(l_module_or_package, p_deep); }
      }
    }
    
    for (i = 0, n = p_modules.length; i < n; i++)
    { l_module_fullname = p_modules[i];
      l_module_name_split = l_module_fullname.split(".");
      l_length = l_module_name_split.length-1;
      l_module_name = l_module_name_split[l_length];
      f_root_pkg_init(l_module_name_split[0]);
      if (l_module_name === "*" || l_module_name === "**")
      { l_module = l_wk_modules;
        
        for (j = 0; j < l_length; j++)
        { l_package_name = l_module_name_split[j];
          l_module = l_module[l_package_name];
          if (!l_module)
          { throw new Error("Package " + l_package_name + " does not exist."); }
        }
        l_modules.push(f_star_begin);
        f_module_add_deep(l_module, l_module_name === "**");
        l_modules.push(f_star_end);
      }
      else
      { l_module = f_module_find(l_module_fullname);
        f_observer_add(l_module);
        l_async.push(f_module_fetch, l_module); 
      }
    }
    l_modules_waiting_for--;
    f_sandbox();
 
    //console.log(l_wk_modules);
    return this;
  };
   
  //////////////////////////////////////////////////////////////////////////////
  // Definition of $wk$ properties.
  //////////////////////////////////////////////////////////////////////////////

  Object.defineProperties
  ($wk$, 
   {"$name$":
     { enumerable:   true,
       configurable: false,
       get:          function(){ return l_library_name; },
       set:          f_error_setter
     },
    "$version$":
      { enumerable:   true,
        configurable: false,
        get:          function(){ return l_version; },
        set:          f_error_setter
      },
    "$__wk_document_complete__$":
      { enumerable:   false,
        configurable: false,
        writable:     false,
        value:        function(p_callback)
                      { function f_aux()
                        { f_module_add({ name: "WaitUntilDocumentComplete",
                                         module: {}
                                       }
                                      );
                          p_callback(); 
                        }
                        if ( document.readyState === "complete" )
                        { f_aux(); }
                        else
                        { p_root_wk.addEventListener("load", f_aux); }
                      }
      },
    "$__wk_document_ready__$":
      { enumerable:   false,
        configurable: false,
        writable:     false,
        value:        function(p_callback)
                      { function f_aux()
                        { f_module_add({ name: "WaitUntilDocumentReady",
                                         module: {}
                                       }
                                      );
                          p_callback(); 
                        }
                        if (   document.readyState === "interactive"
                            || document.readyState === "complete"
                           )
                        { f_aux(); }
                        else
                        { document.addEventListener("DOMContentLoaded", f_aux); }
                      }
      }
  });
  
  //////////////////////////////////////////////////////////////////////////////
  // Pseudo-modules
  //////////////////////////////////////////////////////////////////////////////

  $wk$.$root$                   = p_root_wk;
  $wk$.$C$                      = $C$;
  $wk$.$E$                      = $E$;
  $wk$.$new$                    = f_new;
  $wk$.getOwnPropertyDescriptor = f_get_property_descriptor;
  $wk$.getPropertyDescriptor    = f_get_property_descriptor;
  $wk$.setProperty              = f_set_property;
  $wk$.mixin                    = f_mixin;
  $wk$.moduleAdd                = f_module_add;
  $wk$.rootPackage              = f_root_pkg;
  $wk$.rootPackageInit          = f_root_pkg_init;
  $wk$.pkg                      = f_pkg;
  $wk$.JS                       = JS;
  $wk$.JSON                     = JSON$;
  $wk$.XML                      = XML;
  $wk$.CSS                      = CSS;

  f_mixin($wk$, $wk$.prototype);
  
  $wk$.WKcAsync                 = WKcAsync;

}(this));

////////////////////////////////////////////////////////////////////////////////
// The list of ALL $wk$ modules that may be loaded
////////////////////////////////////////////////////////////////////////////////

$wk$.rootPackage({ "$wk$": { root: this, url: "" } });
$wk$.moduleAdd
({ name: "WaitUntilDocumentReady",    init:   $wk$.$__wk_document_ready__$ },
 { name: "WaitUntilDocumentComplete", init:   $wk$.$__wk_document_complete__$ },
 { name: "$wk$.WKcAsync",  module: { WKcAsync: $wk$.WKcAsync } }
);

////////////////////////////////////////////////////////////////////////////////
// End of the $wk$ object
////////////////////////////////////////////////////////////////////////////////
W. Kowarschick © 2013 (CC BY-NC-SA 3.0: http://creativecommons.org/licenses/by-nc-sa/3.0/)
Documentation generated by JSDoc 3.3.0-dev on Sat Oct 05 2013 15:14:39 GMT+0200 (MESZ) using the DocStrap template.