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