1  /*
     2   * Copyright the original author or authors.
     3   * 
     4   * Licensed under the MOZILLA PUBLIC LICENSE, Version 1.1 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   * 
     8   *      http://www.mozilla.org/MPL/MPL-1.1.html
     9   * 
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  import org.as2lib.core.BasicClass;
    18  import org.as2lib.env.except.IllegalArgumentException;
    19  import org.as2lib.env.reflect.PackageNotFoundException;
    20  import org.as2lib.env.reflect.Cache;
    21  import org.as2lib.env.reflect.PackageInfo;
    22  import org.as2lib.env.reflect.ReflectConfig;
    23  
    24  /**
    25   * {@code PackageAlgorithm} searches for the specified package and returns the
    26   * package info representing the found package.
    27   * 
    28   * <p>To obtain the package info corresponding to package you use this class as
    29   * follows.
    30   *
    31   * <code>
    32   *   var packageAlgorithm:PackageAlgorithm = new PackageAlgorithm();
    33   *   var packageInfoByPackage:PackageInfo = packageAlgorithm.execute(org.as2lib.core);
    34   * </code>
    35   *
    36   * <p>It is also possible to retrieve a package info by name.
    37   *
    38   * <code>
    39   *   packageInfoByName:PackageInfo = packageAlgorithm.executeByName("org.as2lib.core");
    40   * </code>
    41   *
    42   * <p>Already retrieved package infos are stored in a cache. There thus exists only
    43   * one {@code PackageInfo} instance per package. The following traces {@code true}.
    44   * 
    45   * <code>
    46   *   trace(packageInfoByPackage == packageInfoByName);
    47   * </code>
    48   *
    49   * @author Simon Wacker
    50   */
    51  class org.as2lib.env.reflect.algorithm.PackageAlgorithm extends BasicClass {
    52  	
    53  	/** The chache. */
    54  	private var c:Cache;
    55  	
    56  	/** The found packages. */
    57  	private var p;
    58  	
    59  	/**
    60  	 * Constructs a new {@code PackageAlgorithm} instance.
    61  	 */
    62  	public function PackageAlgorithm(Void) {
    63  	}
    64  	
    65  	/**
    66  	 * Sets the cache that is used by the {@link #execute} method to look whether the
    67  	 * package to find is already stored and where to start the search if not.
    68  	 * 
    69  	 * @param cache the new cache
    70  	 */
    71  	public function setCache(cache:Cache):Void {
    72  		c = cache;
    73  	}
    74  	
    75  	/**
    76  	 * Returns the cache set via the {@link #setCache} method or the default cache that
    77  	 * is returned by the {@link ReflectConfig#getCache} method.
    78  	 * 
    79  	 * @return the currently used cache
    80  	 */
    81  	public function getCache(Void):Cache {
    82  		if (!c) c = ReflectConfig.getCache();
    83  		return c;
    84  	}
    85  	
    86  	/**
    87  	 * Executes the search for the passed-in package {@code o} and returns information
    88  	 * about this package.
    89  	 * 
    90  	 * <p>The returned object has the following properties:
    91  	 * <dl>
    92  	 *   <dt>package</dt>
    93  	 *   <dd>The package as {@code Object} that has been searched for, this is the
    94  	 *       passed-in package {@code o}.</dd>
    95  	 *   <dt>name</dt>
    96  	 *   <dd>The name as {@code String} of the searched for package.</dd>
    97  	 *   <dt>parent</dt>
    98  	 *   <dd>The parent represented by a {@ling PackageInfo} instance the searched for
    99  	 *       package is a member of.</dd>
   100  	 * </dl>
   101  	 *
   102  	 * <p>{@code null} will be returned if:
   103  	 * <ul>
   104  	 *   <li>The passed-in package {@code o} is {@code null} or {@code undefined}.</li>
   105  	 *   <li>The searched for package {@code o} could not be found.</li>
   106  	 * </ul>
   107  	 *
   108  	 * <p>The search starts on the package returned by the cache's {@code getRoot}
   109  	 * method, this is by default {@code _global}.
   110  	 * 
   111  	 * @param o the package to return information about
   112  	 * @return an object that contains information about the passed-in package
   113  	 */
   114  	public function execute(o) {
   115  		if (o === null || o === undefined) return null;
   116  		p = null;
   117  		// must set access permissions because by default all package members in _global are hidden
   118  		_global.ASSetPropFlags(o, null, 0, true);
   119  		_global.ASSetPropFlags(o, ["__proto__", "constructor", "__constructor__", "prototype"], 1, true);
   120  		findAndStore(getCache().getRoot(), o);
   121  		return p;
   122  	}
   123  	
   124  	private function findAndStore(a:PackageInfo, o):Boolean {
   125  		var b = a.getPackage();
   126  		var i:String;
   127  		for (i in b) {
   128  			var e:Object = b[i];
   129  			if (typeof(e) == "object") {
   130  				if (e.valueOf() == o.valueOf()) {
   131  					p = new Object();
   132  					p.package = o;
   133  					p.name = i;
   134  					p.parent = a;
   135  					return true;
   136  				}
   137  				var d:PackageInfo = c.getPackage(e);
   138  				if (!d) {
   139  					d = c.addPackage(new PackageInfo(e, i, a));
   140  				}
   141  				if (!d.isParentPackage(a)) {
   142  					// todo: replace recursion with loop
   143  					if (findAndStore(d, o)) {
   144  						return true;
   145  					}
   146  				}
   147  			}
   148  		}
   149  		return false;
   150  	}
   151  	
   152  	/**
   153  	 * Returns the package info representing the package corresponding to the passed-in
   154  	 * package name {@code n}.
   155  	 * 
   156  	 * <p>The name must be fully qualified, that means it must consist of the package's
   157  	 * path as well as its name. For example 'org.as2lib.core'.
   158  	 *
   159  	 * <p>The search starts on the package returned by the {@link Cache#getRoot} method
   160  	 * of the set cache. If this method returns a package info whose {@code getFullName}
   161  	 * method returns {@code null}, {@code undefined} or an empty string {@code "_global"}
   162  	 * is used instead
   163  	 * 
   164  	 * @param n the fully qualified name of the package
   165  	 * @return the package info representing the package corresponding to the passed-in
   166  	 * name
   167  	 * @throws IllegalArgumentException if the passed-in name is {@code null},
   168  	 * {@code undefined} or an empty string or if the object corresponding to the
   169  	 * passed-in name is not of type {@code "object"}
   170  	 * @throws PackageNotFoundException if a package with the passed-in name could not
   171  	 * be found
   172  	 */
   173  	public function executeByName(n:String):PackageInfo {
   174  		if (!n) throw new IllegalArgumentException("The passed-in package name '" + n + "' is not allowed to be null, undefined or an empty string.", this, arguments);
   175  		var p:PackageInfo = getCache().getRoot();
   176  		var x:String = p.getFullName();
   177  		if (!x) x = "_global";
   178  		var f:Object = eval(x + "." + n);
   179  		if (f === null || f === undefined) {
   180  			throw new PackageNotFoundException("A package with the name '" + n + "' could not be found.", this, arguments);
   181  		}
   182  		if (typeof(f) != "object") throw new IllegalArgumentException("The object corresponding to the passed-in package name '" + n + "' is not of type object.", this, arguments);
   183  		var r:PackageInfo = c.getPackage(f);
   184  		if (r) return r;
   185  		var a:Array = n.split(".");
   186  		var g:Object = p.getPackage();
   187  		for (var i:Number = 0; i < a.length; i++) {
   188  			g = g[a[i]];
   189  			p = c.addPackage(new PackageInfo(g, a[i], p));
   190  		}
   191  		return p;
   192  	}
   193  	
   194  }