001/* Statement.java
002   Copyright (C) 2004, 2005, 2006, Free Software Foundation, Inc.
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010 
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038
039package java.beans;
040
041import gnu.java.lang.CPStringBuilder;
042
043import java.lang.reflect.Array;
044import java.lang.reflect.Constructor;
045import java.lang.reflect.Method;
046
047/**
048 * <p>A Statement captures the execution of an object method.  It stores
049 * the object, the method to call, and the arguments to the method and
050 * provides the ability to execute the method on the object, using the
051 * provided arguments.</p>
052 *
053 * @author Jerry Quinn (jlquinn@optonline.net)
054 * @author Robert Schuster (robertschuster@fsfe.org)
055 * @since 1.4
056 */
057public class Statement
058{
059  private Object target;
060  private String methodName;
061  private Object[] arguments;
062
063  /**
064   * One or the other of these will get a value after execute is
065   * called once, but not both.
066   */
067  private transient Method method;
068  private transient Constructor ctor;
069
070  /**
071   * <p>Constructs a statement representing the invocation of
072   * object.methodName(arg[0], arg[1], ...);</p>
073   *
074   * <p>If the argument array is null it is replaced with an
075   * array of zero length.</p>
076   *
077   * @param target The object to invoke the method on.
078   * @param methodName The object method to invoke.
079   * @param arguments An array of arguments to pass to the method.
080   */
081  public Statement(Object target, String methodName, Object[] arguments)
082  {
083    this.target = target;
084    this.methodName = methodName;
085    this.arguments = (arguments != null) ? arguments : new Object[0];
086  }
087
088  /**
089   * Execute the statement.
090   *
091   * <p>Finds the specified method in the target object and calls it with
092   * the arguments given in the constructor.</p>
093   *
094   * <p>The most specific method according to the JLS(15.11) is used when
095   * there are multiple methods with the same name.</p>
096   *
097   * <p>Execute performs some special handling for methods and
098   * parameters:
099   * <ul>
100   * <li>Static methods can be executed by providing the class as a
101   * target.</li>
102   *
103   * <li>The method name new is reserved to call the constructor 
104   * new() will construct an object and return it.  Not useful unless
105   * an expression :-)</li>
106   *
107   * <li>If the target is an array, get and set as defined in
108   * java.util.List are recognized as valid methods and mapped to the
109   * methods of the same name in java.lang.reflect.Array.</li>
110   *
111   * <li>The native datatype wrappers Boolean, Byte, Character, Double,
112   * Float, Integer, Long, and Short will map to methods that have
113   * native datatypes as parameters, in the same way as Method.invoke.
114   * However, these wrappers also select methods that actually take
115   * the wrapper type as an argument.</li>
116   * </ul>
117   * </p>
118   *
119   * <p>The Sun spec doesn't deal with overloading between int and
120   * Integer carefully.  If there are two methods, one that takes an
121   * Integer and the other taking an int, the method chosen is not
122   * specified, and can depend on the order in which the methods are
123   * declared in the source file.</p>
124   *
125   * @throws Exception if an exception occurs while locating or
126   *                   invoking the method.
127   */
128  public void execute() throws Exception
129  {
130    doExecute();
131  }
132  
133  private static Class wrappers[] = 
134    {
135      Boolean.class, Byte.class, Character.class, Double.class, Float.class,
136      Integer.class, Long.class, Short.class
137    };
138
139  private static Class natives[] = 
140    {
141      Boolean.TYPE, Byte.TYPE, Character.TYPE, Double.TYPE, Float.TYPE,
142      Integer.TYPE, Long.TYPE, Short.TYPE
143    };
144
145  /** Given a wrapper class, return the native class for it.
146   * <p>For example, if <code>c</code> is <code>Integer</code>, 
147   * <code>Integer.TYPE</code> is returned.</p>
148   */
149  private Class unwrap(Class c)
150  {
151    for (int i = 0; i < wrappers.length; i++)
152      if (c == wrappers[i])
153        return natives[i];
154    return null;
155  }
156
157  /** Returns <code>true</code> if all args can be assigned to
158   * <code>params</code>, <code>false</code> otherwise.
159   *
160   * <p>Arrays are guaranteed to be the same length.</p>
161   */
162  private boolean compatible(Class[] params, Class[] args)
163  {
164    for (int i = 0; i < params.length; i++)
165      {
166    // Argument types are derived from argument values. If one of them was
167    // null then we cannot deduce its type. However null can be assigned to
168    // any type.
169    if (args[i] == null)
170      continue;
171    
172    // Treat Integer like int if appropriate
173        Class nativeType = unwrap(args[i]);
174        if (nativeType != null && params[i].isPrimitive()
175            && params[i].isAssignableFrom(nativeType))
176          continue;
177        if (params[i].isAssignableFrom(args[i]))
178          continue;
179
180        return false;
181      }
182    return true;
183  }
184
185  /**
186   * Returns <code>true</code> if the method arguments in first are
187   * more specific than the method arguments in second, i.e. all
188   * arguments in <code>first</code> can be assigned to those in
189   * <code>second</code>.
190   *
191   * <p>A method is more specific if all parameters can also be fed to
192   * the less specific method, because, e.g. the less specific method
193   * accepts a base class of the equivalent argument for the more
194   * specific one.</p>
195   *
196   * @param first a <code>Class[]</code> value
197   * @param second a <code>Class[]</code> value
198   * @return a <code>boolean</code> value
199   */
200  private boolean moreSpecific(Class[] first, Class[] second)
201  {
202    for (int j=0; j < first.length; j++)
203      {
204        if (second[j].isAssignableFrom(first[j]))
205          continue;
206        return false;
207      }
208    return true;
209  }
210
211  final Object doExecute() throws Exception
212  {
213    Class klazz = (target instanceof Class)
214        ? (Class) target : target.getClass();
215    Object args[] = (arguments == null) ? new Object[0] : arguments;
216    Class argTypes[] = new Class[args.length];
217    
218    // Retrieve type or use null if the argument is null. The null argument
219    // type is later used in compatible().
220    for (int i = 0; i < args.length; i++)
221      argTypes[i] = (args[i] != null) ? args[i].getClass() : null;
222
223    if (target.getClass().isArray())
224      {
225        // FIXME: invoke may have to be used.  For now, cast to Number
226        // and hope for the best.  If caller didn't behave, we go boom
227        // and throw the exception.
228        if (methodName.equals("get") && argTypes.length == 1)
229          return Array.get(target, ((Number)args[0]).intValue());
230        if (methodName.equals("set") && argTypes.length == 2)
231          {
232            Object obj = Array.get(target, ((Number)args[0]).intValue());
233            Array.set(target, ((Number)args[0]).intValue(), args[1]);
234            return obj;
235          }
236        throw new NoSuchMethodException("No matching method for statement " + toString());
237      }
238
239    // If we already cached the method, just use it.
240    if (method != null)
241      return method.invoke(target, args);
242    else if (ctor != null)
243      return ctor.newInstance(args);
244
245    // Find a matching method to call.  JDK seems to go through all
246    // this to find the method to call.
247
248    // if method name or length don't match, skip
249    // Need to go through each arg
250    // If arg is wrapper - check if method arg is matchable builtin
251    //  or same type or super
252    //  - check that method arg is same or super
253
254    if (methodName.equals("new") && target instanceof Class)
255      {
256        Constructor ctors[] = klazz.getConstructors();
257        for (int i = 0; i < ctors.length; i++)
258          {
259            // Skip methods with wrong number of args.
260            Class ptypes[] = ctors[i].getParameterTypes();
261
262            if (ptypes.length != args.length)
263              continue;
264
265            // Check if method matches
266            if (!compatible(ptypes, argTypes))
267              continue;
268
269            // Use method[i] if it is more specific. 
270            // FIXME: should this check both directions and throw if
271            // neither is more specific?
272            if (ctor == null)
273              {
274                ctor = ctors[i];
275                continue;
276              }
277            Class mptypes[] = ctor.getParameterTypes();
278            if (moreSpecific(ptypes, mptypes))
279              ctor = ctors[i];
280          }
281        if (ctor == null)
282          throw new InstantiationException("No matching constructor for statement " + toString());
283        return ctor.newInstance(args);
284      }
285
286    Method methods[] = klazz.getMethods();
287
288    for (int i = 0; i < methods.length; i++)
289      {
290        // Skip methods with wrong name or number of args.
291        if (!methods[i].getName().equals(methodName))
292          continue;
293        Class ptypes[] = methods[i].getParameterTypes();
294        if (ptypes.length != args.length)
295          continue;
296
297        // Check if method matches
298        if (!compatible(ptypes, argTypes))
299          continue;
300
301        // Use method[i] if it is more specific. 
302        // FIXME: should this check both directions and throw if
303        // neither is more specific?
304        if (method == null)
305          {
306            method = methods[i];
307            continue;
308          }
309        Class mptypes[] = method.getParameterTypes();
310        if (moreSpecific(ptypes, mptypes))
311          method = methods[i];
312      }
313    if (method == null)
314      throw new NoSuchMethodException("No matching method for statement " + toString());
315
316    // If we were calling Class.forName(String) we intercept and call the
317    // forName-variant that allows a ClassLoader argument. We take the
318    // system classloader (aka application classloader) here to make sure
319    // that application defined classes can be resolved. If we would not
320    // do that the Class.forName implementation would use the class loader
321    // of java.beans.Statement which is <null> and cannot resolve application
322    // defined classes.
323    if (method.equals(
324           Class.class.getMethod("forName", new Class[] { String.class })))
325      return Class.forName(
326               (String) args[0], true, ClassLoader.getSystemClassLoader());
327
328    try {
329    return method.invoke(target, args);
330    } catch(IllegalArgumentException iae){
331      System.err.println("method: " + method);
332      
333      for(int i=0;i<args.length;i++){
334        System.err.println("args[" + i + "]: " + args[i]);
335      }
336      throw iae;
337    }
338  }
339
340  
341
342  /** Return the statement arguments. */
343  public Object[] getArguments() { return arguments; }
344
345  /** Return the statement method name. */
346  public String getMethodName() { return methodName; }
347
348  /** Return the statement object. */
349  public Object getTarget() { return target; }
350
351  /** 
352   * Returns a string representation of this <code>Statement</code>. 
353   * 
354   * @return A string representation of this <code>Statement</code>. 
355   */
356  public String toString()
357  {
358    CPStringBuilder result = new CPStringBuilder(); 
359
360    String targetName;
361    if (target != null)
362      targetName = target.getClass().getSimpleName();
363    else 
364      targetName = "null";
365
366    result.append(targetName);
367    result.append(".");
368    result.append(methodName);
369    result.append("(");
370
371    String sep = "";
372    for (int i = 0; i < arguments.length; i++)
373      {
374        result.append(sep);
375        result.append(
376          ( arguments[i] == null ) ? "null" : 
377            ( arguments[i] instanceof String ) ? "\"" + arguments[i] + "\"" :
378            arguments[i].getClass().getSimpleName());
379        sep = ", ";
380      }
381    result.append(");");
382
383    return result.toString();
384  }
385  
386}