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 }