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.ClassNotFoundException;
    20  import org.as2lib.env.reflect.Cache;
    21  import org.as2lib.env.reflect.PackageInfo;
    22  import org.as2lib.env.reflect.ClassInfo;
    23  import org.as2lib.env.reflect.ReflectConfig;
    24  
    25  /**
    26   * {@code ClassAlgorithm} searches for the class of a specific instance or class
    27   * and returns information about that class.
    28   * 
    29   * <p>This class is rather cumbersome to use. It is recommended to use the static
    30   * {@link ClassInfo#forObject}, {@link ClassInfo#forInstance}, {@link ClassInfo#forClass}
    31   * and {@link ClassInfo#forName} methods instead. They offer more sophisticated
    32   * return values and do also store {@code ClassInfo} instances retrieved by classes
    33   * or instances and not only those by name like this algorithm does.
    34   *
    35   * <p>To obtain information corresponding to an instance or a class you can use
    36   * this class as follows.
    37   * 
    38   * <code>
    39   *   var myInstance:MyClass = new MyClass();
    40   *   var classAlgorithm:ClassAlgorithm = new ClassAlgorithm();
    41   *   var infoByInstance:Object = classAlgorithm.execute(myInstance);
    42   *   var infoByClass:Object = classAlgorithm.execute(MyClass);
    43   * </code>
    44   *
    45   * <p>It is also possible to retrieve a class info by name.
    46   *
    47   * <code>
    48   *   classInfoByName:ClassInfo = classAlgorithm.executeByName("MyClass");
    49   * </code>
    50   *
    51   * <p>If the class is not contained in the root/default package you must specify
    52   * the whole path / its namespace.
    53   * 
    54   * <code>
    55   *   classInfoByName:ClassInfo = classAlgorithm.executeByName("org.as2lib.MyClass");
    56   * </code>
    57   *
    58   * <p>Already retrieved class infos are stored in a cache. There thus exists only
    59   * one {@code ClassInfo} instance per class. Note that the {@link #execute} method
    60   * does not return {@code ClassInfo} instances and does thus not store the found
    61   * information.
    62   * 
    63   * @author Simon Wacker
    64   */
    65  class org.as2lib.env.reflect.algorithm.ClassAlgorithm extends BasicClass {
    66  	
    67  	/** The cache to store class and package infos. */
    68  	private var c:Cache;
    69  	
    70  	/** The temporary result. */
    71  	private var r;
    72  	
    73  	/**
    74  	 * Constructs a new {@code ClassAlgorithm} instance.
    75  	 */
    76  	public function ClassAlgorithm(Void) {
    77  	}
    78  	
    79  	/**
    80  	 * Sets the cache that is used by the {@link #executeByName} method to look whether
    81  	 * the class the shall be found is already stored.
    82  	 *
    83  	 * <p>This cache also determines where the search for a class starts.
    84  	 * 
    85  	 * @param cache the new cache
    86  	 */
    87  	public function setCache(cache:Cache):Void {
    88  		c = cache;
    89  	}
    90  	
    91  	/**
    92  	 * Returns the cache set via the {@link #setCache} method or the default cache that
    93  	 * is returned by the {@link ReflectConfig#getCache} method.
    94  	 * 
    95  	 * @return the currently used cache
    96  	 */
    97  	public function getCache(Void):Cache {
    98  		if (!c) c = ReflectConfig.getCache();
    99  		return c;
   100  	}
   101  	
   102  	/**
   103  	 * @overload #executeByClass
   104  	 * @overload #executeByInstance
   105  	 */
   106  	public function execute(d) {
   107  		if (typeof(d) == "function") {
   108  			return executeByClass(d);
   109  		}
   110  		return executeByInstance(d);
   111  	}
   112  	
   113  	/**
   114  	 * Executes the search for the passed-in class {@code d} and returns information
   115  	 * about this class.
   116  	 * 
   117  	 * <p>The returned object has the following properties:
   118  	 * <dl>
   119  	 *   <dt>clazz</dt>
   120  	 *   <dd>The class as {@code Function} that has been searched for, this is the
   121  	 *       passed-in class {@code d}.</dd>
   122  	 *   <dt>name</dt>
   123  	 *   <dd>The name as {@code String} of the searched for class.</dd>
   124  	 *   <dt>package</dt>
   125  	 *   <dd>The package represented by a {@link PackageInfo} instance the class is
   126  	 *       a member of.</dd>
   127  	 * </dl>
   128  	 *
   129  	 * <p>{@code null} will be returned if:
   130  	 * <ul>
   131  	 *   <li>The passed-in class {@code d} is {@code null} or {@code undefined}.</li>
   132  	 *   <li>The passed-in class {@code d} could not be found.</li>
   133  	 * </ul>
   134  	 *
   135  	 * <p>The search starts on the package returned by the {@link Cache#getRoot} method
   136  	 * of the set cache. If this method returns a package info whose {@code getPackage}
   137  	 * method returns {@code null} or {@code undefined} {@code _global} is used instead.
   138  	 * 
   139  	 * @param d the class to return information about
   140  	 * @return an object that contains information about the passed-in class {@code d}
   141  	 * @see #getCache
   142  	 */
   143  	public function executeByClass(d:Function) {
   144  		if (d === null || d === undefined) return null;
   145  		return executeByComparator(function(f:Function) {
   146  			// use f.valueOf() because this allows one to overwrite the original class,
   147  			// to add a valueOf method that returns the original class and still find
   148  			// the correct class at run-time (this is for example used by the aop framework)
   149  			return f.valueOf() == d.valueOf();
   150  		});
   151  	}
   152  	
   153  	/**
   154  	 * Executes the search for the class the passed-in object {@code d} is an instance
   155  	 * of and returns information about that class.
   156  	 * 
   157  	 * <p>The returned object has the following properties:
   158  	 * <dl>
   159  	 *   <dt>clazz</dt>
   160  	 *   <dd>The class as {@code Function} that has been searched for.</dd>
   161  	 *   <dt>name</dt>
   162  	 *   <dd>The name as {@code String} of the searched for class.</dd>
   163  	 *   <dt>package</dt>
   164  	 *   <dd>The package represented by a {@link PackageInfo} instance the class is
   165  	 *       a member of.</dd>
   166  	 * </dl>
   167  	 *
   168  	 * <p>{@code null} will be returned if:
   169  	 * <ul>
   170  	 *   <li>The passed-in instance {@code d} is {@code null} or {@code undefined}.</li>
   171  	 *   <li>The class of the passed-in instance could not be found.</li>
   172  	 * </ul>
   173  	 *
   174  	 * <p>The search starts on the package returned by the {@link Cache#getRoot} method
   175  	 * of the set cache. If this method returns a package info whose {@code getPackage}
   176  	 * method returns {@code null} or {@code undefined} {@code _global} is used instead.
   177  	 *
   178  	 * @param d the instance of the class to return information about
   179  	 * @return an object that contains information about the class the passed-in object
   180  	 * {@code d} is an instance of
   181  	 * @see #getCache
   182  	 */
   183  	public function executeByInstance(d) {
   184  		// not 'if (!d)' because 'd' could be en empty string or a boolean
   185  		// 'valueOf' method of 'd' could return 'null' or 'undefined' thus strict eval is used
   186  		if (d === null || d === undefined) return null;
   187  		return executeByComparator(function(f:Function) {
   188  			return f.prototype === d.__proto__;
   189  		});
   190  	}
   191  	
   192  	/**
   193  	 * Executes the search for a class and returns information about that class.
   194  	 * 
   195  	 * <p>The returned object has the following properties:
   196  	 * <dl>
   197  	 *   <dt>clazz</dt>
   198  	 *   <dd>The class as {@code Function} that has been searched for.</dd>
   199  	 *   <dt>name</dt>
   200  	 *   <dd>The name as {@code String} of the searched for class.</dd>
   201  	 *   <dt>package</dt>
   202  	 *   <dd>The package represented by a {@ling PackageInfo} instance the class is
   203  	 *       a member of.</dd>
   204  	 * </dl>
   205  	 *
   206  	 * <p>{@code null} will be returned if:
   207  	 * <ul>
   208  	 *   <li>The passed-in comparator {@code v} method is {@code null} or {@code undefined}.</li>
   209  	 *   <li>The searched for class could not be found.</li>
   210  	 * </ul>
   211  	 * 
   212  	 * <p>The search starts on the package returned by the {@link Cache#getRoot} method
   213  	 * of the set cache. If this method returns a package info whose {@code getPackage}
   214  	 * method returns {@code null} or {@code undefined} {@code _global} is used instead.
   215  	 *
   216  	 * <p>The passed-in comparator is invoked for every found class to determine whether
   217  	 * it is the right one or not. The comparator method gets passed the found class and
   218  	 * must return {@code true} or {@code false}. If it returns {@code true} the
   219  	 * algorithm stops and returns the information about this class.
   220  	 * @param v the comparator to determine the correct class
   221  	 * @return an object that contains information about the class
   222  	 * @see #getCache
   223  	 */
   224  	public function executeByComparator(v:Function) {
   225  		if (!v) return null;
   226  		r = null;
   227  		var b:PackageInfo = getCache().getRoot();
   228  		var a:Object = b.getPackage();
   229  		if (!a) a = _global;
   230  		_global.ASSetPropFlags(a, null, 0, true);
   231  		_global.ASSetPropFlags(a, ["__proto__", "constructor", "__constructor__", "prototype"], 1, true);
   232  		findAndStore(b, v);
   233  		return r;
   234  	}
   235  	
   236  	private function findAndStore(a:PackageInfo, v:Function):Boolean {
   237  		var p = a.getPackage();
   238  		var i:String;
   239  		for (i in p) {
   240  			var f = p[i];
   241  			if (typeof(f) == "function") {
   242  				if (v(f)) {
   243  					// flex stores every class in _global and in its actual package
   244  					// e.g. org.as2lib.core.BasicClass is stored in _global with name org_as2lib_core_BasicClass
   245  					// this if-clause excludes these extra stored classes
   246  					if (!eval("_global." + i.split("_").join(".")) || i.indexOf("_") < 0) {
   247  						r = new Object();
   248  						r.clazz = f;
   249  						r.name = i;
   250  						r.package = a;
   251  						return true;
   252  					}
   253  				}
   254  			} else if (typeof(f) == "object") {
   255  				var e:PackageInfo = c.getPackage(f);
   256  				if (!e) {
   257  					e = c.addPackage(new PackageInfo(f, i, a));
   258  				}
   259  				if (!e.isParentPackage(a)) {
   260  					// todo: replace recursion with loop
   261  					if (findAndStore(e, v)) {
   262  						return true;
   263  					}
   264  				}
   265  			}
   266  		}
   267  		return false;
   268  	}
   269  	
   270  	/**
   271  	 * Returns the class info representing the class corresponding to the passed-in
   272  	 * class name {@code n}.
   273  	 * 
   274  	 * <p>The class name must be fully qualified, that means it must consist of the
   275  	 * class's path (namespace) as well as its name. For example 'org.as2lib.core.BasicClass'.
   276  	 *
   277  	 * <p>The search starts on the package returned by the {@link Cache#getRoot} method
   278  	 * of the set cache. If this method returns a package info whose {@code getFullName}
   279  	 * method returns {@code null} or {@code undefined} {@code "_global"} is used instead.
   280  	 * 
   281  	 * @param n the fully qualified name of the class
   282  	 * @return the class info representing the class corresponding to the passed-in name
   283  	 * @throws IllegalArgumentException if the passed-in name is {@code null}, {@code undefined}
   284  	 * or an empty string or if the object corresponding to the passed-in name is not of
   285  	 * type function
   286  	 * @throws ClassNotFoundException if a class with the passed-in name could not be found
   287  	 */
   288  	public function executeByName(n:String):ClassInfo {
   289  		if (!n) throw new IllegalArgumentException("The passed-in class name '" + n + "' is not allowed to be null, undefined or an empty string.", this, arguments);
   290  		var p:PackageInfo = getCache().getRoot();
   291  		var x:String = p.getFullName();
   292  		if (!x) x = "_global";
   293  		var f:Function = eval(x + "." + n);
   294  		if (!f) throw new ClassNotFoundException("A class with the name '" + n + "' could not be found.", this, arguments);
   295  		if (typeof(f) != "function") throw new IllegalArgumentException("The object corresponding to the passed-in class name '" + n + "' is not of type function.", this, arguments);
   296  		var r:ClassInfo = c.getClassByClass(f);
   297  		if (r) return r;
   298  		var a:Array = n.split(".");
   299  		var g:Object = p.getPackage();
   300  		for (var i:Number = 0; i < a.length; i++) {
   301  			if (i == a.length-1) {
   302  				return c.addClass(new ClassInfo(f, a[i], p));
   303  			} else {
   304  				g = g[a[i]];
   305  				p = c.addPackage(new PackageInfo(g, a[i], p));
   306  			}
   307  		}
   308  		return null;
   309  		// unreachable!!!
   310  	}
   311  	
   312  }