001    /* Statement.java
002       Copyright (C) 2004, 2005, 2006, Free Software Foundation, Inc.
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    
039    package java.beans;
040    
041    import gnu.java.lang.CPStringBuilder;
042    
043    import java.lang.reflect.Array;
044    import java.lang.reflect.Constructor;
045    import 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     */
057    public 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    }