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.util.Stringifier;
    19  import org.as2lib.env.reflect.TypeInfo;
    20  import org.as2lib.env.reflect.TypeMemberInfo;
    21  import org.as2lib.env.reflect.stringifier.MethodInfoStringifier;
    22  
    23  /**
    24   * {@code MethodInfo} represents a method.
    25   * 
    26   * <p>{@code MethodInfo} instances for specific methods can be obtained using the
    27   * {@link ClassInfo#getMethods} or {@link ClassInfo#getMethod} methods. That means
    28   * you first have to get a class info for the class that declares or inherits the
    29   * method. You can therefor use the {@link ClassInfo#forObject}, {@link ClassInfo#forClass},
    30   * {@link ClassInfo#forInstance} and {@link ClassInfo#forName} methods.
    31   * 
    32   * <p>When you have obtained the method info you can use it to get information about
    33   * the method.
    34   *
    35   * <code>
    36   *   trace("Method name: " + methodInfo.getName());
    37   *   trace("Declaring type: " + methodInfo.getDeclaringType().getFullName());
    38   *   trace("Is Static?: " + methodInfo.isStatic());
    39   * </code>
    40   *
    41   * @author Simon Wacker
    42   */
    43  class org.as2lib.env.reflect.MethodInfo extends BasicClass implements TypeMemberInfo {
    44  	
    45  	/** The method info stringifier. */
    46  	private static var stringifier:Stringifier;
    47  	
    48  	/**
    49  	 * The invoker method used to invoke this method. This invoker is invoked on
    50  	 * different scopes, never on this scope.
    51  	 * 
    52  	 * <p>This invoker removes itself, before executing the method, from the object it
    53  	 * was assigned to. It expects itself to have the name {@code "__as2lib__invoker"}.
    54  	 * 
    55  	 * @param object the object that holds this invoker method
    56  	 * @param method the method to invoke on the {@code super} object
    57  	 * @param args the arguments to use for the invocation
    58  	 * @return the result of the invocation of {@code method} with {@code args} on the
    59  	 * {@code super} scope
    60  	 */
    61  	private var INVOKER:Function = function(object, method:Function, args:Array) {
    62  		// deletes the variable '__as2lib__invoker'; deletes the reference to this function
    63  		delete object.__as2lib__invoker;
    64  		// ('super' is not accessible from this scope, at least that's the compiler error) <-- this was at the time INVOKER was static
    65  		// eval("su" + "per") is not supported by MTASC. INVOKER must thus be an instance variable because normal
    66  		// 'super' usage is not allowed for per class / static functions
    67  		return method.apply(super, args);
    68  	};
    69  	
    70  	/**
    71  	 * Returns the stringifier used to stringify method infos.
    72  	 *
    73  	 * <p>If no custom stringifier has been set via the {@link #setStringifier} method,
    74  	 * a instance of the default {@code MethodInfoStringifier} class is returned.
    75  	 * 
    76  	 * @return the stringifier that stringifies method infos
    77  	 */
    78  	public static function getStringifier(Void):Stringifier {
    79  		if (!stringifier) stringifier = new MethodInfoStringifier();
    80  		return stringifier;
    81  	}
    82  	
    83  	/**
    84  	 * Sets the stringifier used to stringify method infos.
    85  	 *
    86  	 * <p>If {@code methodInfoStringifier} is {@code null} or {@code undefined}
    87  	 * {@link #getStringifier} will return the default stringifier.
    88  	 * 
    89  	 * @param methodInfoStringifier the stringifier that stringifies method infos
    90  	 */
    91  	public static function setStringifier(methodInfoStringifier:MethodInfoStringifier):Void {
    92  		stringifier = methodInfoStringifier;
    93  	}
    94  	
    95  	/** The name of this method. */
    96  	private var name:String;
    97  	
    98  	/** The concrete method. */
    99  	private var method:Function;
   100  	
   101  	/** The type that declares this method. */
   102  	private var declaringType:TypeInfo;
   103  	
   104  	/** A flag representing whether this method is static or not. */
   105  	private var staticFlag:Boolean;
   106  	
   107  	/**
   108  	 * Constructs a new {@code MethodInfo} instance.
   109  	 *
   110  	 * <p>All arguments are allowed to be {@code null}. But keep in mind that not all
   111  	 * methods will function properly if one is.
   112  	 * 
   113  	 * <p>If {@code method} is not specified, it will be resolved at run-time everytime
   114  	 * it is needed. This means that the concrete method will always be up-to-date even
   115  	 * if you have overwritten it.
   116  	 * 
   117  	 * @param name the name of the method
   118  	 * @param declaringType the declaring type of the method
   119  	 * @param staticFlag determines whether the method is static
   120  	 * @param method (optional) the concrete method
   121  	 */
   122  	public function MethodInfo(name:String,
   123  							   declaringType:TypeInfo,
   124  							   staticFlag:Boolean,
   125  							   method:Function) {
   126  		this.name = name;
   127  		this.declaringType = declaringType;
   128  		this.staticFlag = staticFlag;
   129  		this.method = method;
   130  	}
   131  	
   132  	/**
   133  	 * Returns the name of this method.
   134  	 *
   135  	 * @return the name of this method
   136  	 */
   137  	public function getName(Void):String {
   138  		return name;
   139  	}
   140  	
   141  	/**
   142  	 * Returns the full name of this method.
   143  	 * 
   144  	 * <p>The full name is the fully qualified name of the declaring type plus the name
   145  	 * of this method.
   146  	 *
   147  	 * @return the full name of this method
   148  	 */
   149  	public function getFullName(Void):String {
   150  		if (declaringType.getFullName()) {
   151  			return declaringType.getFullName() + "." + name;
   152  		}
   153  		return name;
   154  	}
   155  	
   156  	/**
   157  	 * Returns the concrete method this instance represents.
   158  	 *
   159  	 * <p>If the concrete method was not specified on construction it will be resolved
   160  	 * on run-time by this method everytime asked for. The returned method is thus
   161  	 * always the current method on the declaring type.
   162  	 *
   163  	 * @return the concrete method
   164  	 */
   165  	public function getMethod(Void):Function {
   166  		if (method !== undefined) {
   167  			return method;
   168  		}
   169  		var t:Function = declaringType.getType();
   170  		if (staticFlag) {
   171  			if (t[name] != t.__proto__[name]) {
   172  				return t[name];
   173  			}
   174  		}
   175  		var p:Object = t.prototype;
   176  		if (p[name] != p.__proto__[name]) {
   177  			return p[name];
   178  		}
   179  		return null;
   180  	}
   181  	
   182  	/**
   183  	 * Returns the type that declares this method.
   184  	 *
   185  	 * @return the type that declares this method
   186  	 */
   187  	public function getDeclaringType(Void):TypeInfo {
   188  		return declaringType;
   189  	}
   190  	
   191  	/**
   192  	 * Invokes this method on the passed-in {@code scope} passing the given {@code args}.
   193  	 * 
   194  	 * <p>The object referenced by {@code this} in this method is the object this method
   195  	 * is invoked on, its / the passed-in {@code scope}.
   196  	 * 
   197  	 * @param scope the {@code this}-scope for the method invocation
   198  	 * @param args the arguments to pass-to the method on invocation
   199  	 * @return the return value of the method invocation
   200  	 */
   201  	public function invoke(scope, args:Array) {
   202  		// there is no super bug with apply and static methods because 'super' is not allowed in static methods
   203  		if (!staticFlag) {
   204  			var t:Function = declaringType.getType();
   205  			// super bug can only be fixed if scope is an instance of the declaring type
   206  			// otherwise everything is messed up anyway
   207  			if (scope instanceof t) {
   208  				var p:Object = t.prototype;
   209  				// if scope is a direct instance of the declaring type super works as expected
   210  				if (scope.__proto__ != p) {
   211  					var s = scope.__proto__;
   212  					while (s.__proto__ != p) {
   213  						s = s.__proto__;
   214  					}
   215  					s.__as2lib__invoker = INVOKER;
   216  					return scope.__as2lib__invoker(s, getMethod(), args);
   217  				}
   218  			}
   219  		}
   220  		return getMethod().apply(scope, args);
   221  	}
   222  	
   223  	/**
   224  	 * Returns whether this method is static or not.
   225  	 * 
   226  	 * <p>Static methods are methods per type.
   227  	 *
   228  	 * <p>Non-Static methods are methods per instance.
   229  	 *
   230  	 * @return {@code true} if this method is static else {@code false}
   231  	 */
   232  	public function isStatic(Void):Boolean {
   233  		return staticFlag;
   234  	}
   235  	
   236  	/**
   237  	 * Returns a method info that reflects the current state of this method info.
   238  	 * 
   239  	 * @return a snapshot of this method info
   240  	 */
   241  	public function snapshot(Void):MethodInfo {
   242  		return new MethodInfo(name, declaringType, staticFlag, getMethod());
   243  	}
   244  	
   245  	/**
   246  	 * Returns the string representation of this method.
   247  	 *
   248  	 * <p>The string representation is obtained via the stringifier returned by the
   249  	 * static {@link #getStringifier} method.
   250  	 * 
   251  	 * @return the string representation of this method
   252  	 */
   253  	public function toString():String {
   254  		return getStringifier().execute(this);
   255  	}
   256  	
   257  }