Statement.java 12.8 KB
Newer Older
1
/* Statement.java
2
   Copyright (C) 2004, 2005, 2006, Free Software Foundation, Inc.
Tom Tromey committed
3 4 5 6 7 8 9

This file is part of GNU Classpath.

GNU Classpath is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
10

Tom Tromey committed
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
GNU Classpath is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with GNU Classpath; see the file COPYING.  If not, write to the
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA.

Linking this library statically or dynamically with other modules is
making a combined work based on this library.  Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.

As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module.  An independent module is a module which is not derived from
or based on this library.  If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so.  If you do not wish to do so, delete this
exception statement from your version. */


package java.beans;

41 42
import gnu.java.lang.CPStringBuilder;

Tom Tromey committed
43 44 45 46 47
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

/**
48
 * <p>A Statement captures the execution of an object method.  It stores
Tom Tromey committed
49 50
 * the object, the method to call, and the arguments to the method and
 * provides the ability to execute the method on the object, using the
51
 * provided arguments.</p>
Tom Tromey committed
52
 *
53 54
 * @author Jerry Quinn (jlquinn@optonline.net)
 * @author Robert Schuster (robertschuster@fsfe.org)
Tom Tromey committed
55 56 57 58 59 60 61 62
 * @since 1.4
 */
public class Statement
{
  private Object target;
  private String methodName;
  private Object[] arguments;

63 64 65 66
  /**
   * One or the other of these will get a value after execute is
   * called once, but not both.
   */
Tom Tromey committed
67 68 69 70
  private transient Method method;
  private transient Constructor ctor;

  /**
Tom Tromey committed
71 72 73 74 75
   * <p>Constructs a statement representing the invocation of
   * object.methodName(arg[0], arg[1], ...);</p>
   *
   * <p>If the argument array is null it is replaced with an
   * array of zero length.</p>
Tom Tromey committed
76 77 78 79 80 81 82 83 84
   *
   * @param target The object to invoke the method on.
   * @param methodName The object method to invoke.
   * @param arguments An array of arguments to pass to the method.
   */
  public Statement(Object target, String methodName, Object[] arguments)
  {
    this.target = target;
    this.methodName = methodName;
Tom Tromey committed
85
    this.arguments = (arguments != null) ? arguments : new Object[0];
Tom Tromey committed
86 87 88 89 90
  }

  /**
   * Execute the statement.
   *
91 92
   * <p>Finds the specified method in the target object and calls it with
   * the arguments given in the constructor.</p>
Tom Tromey committed
93
   *
94 95
   * <p>The most specific method according to the JLS(15.11) is used when
   * there are multiple methods with the same name.</p>
Tom Tromey committed
96
   *
97
   * <p>Execute performs some special handling for methods and
Tom Tromey committed
98
   * parameters:
99 100 101
   * <ul>
   * <li>Static methods can be executed by providing the class as a
   * target.</li>
Tom Tromey committed
102
   *
103
   * <li>The method name new is reserved to call the constructor
Tom Tromey committed
104
   * new() will construct an object and return it.  Not useful unless
105
   * an expression :-)</li>
Tom Tromey committed
106
   *
107
   * <li>If the target is an array, get and set as defined in
Tom Tromey committed
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>
Tom Tromey committed
110
   *
111
   * <li>The native datatype wrappers Boolean, Byte, Character, Double,
Tom Tromey committed
112 113 114
   * Float, Integer, Long, and Short will map to methods that have
   * native datatypes as parameters, in the same way as Method.invoke.
   * However, these wrappers also select methods that actually take
115 116 117
   * the wrapper type as an argument.</li>
   * </ul>
   * </p>
Tom Tromey committed
118
   *
119
   * <p>The Sun spec doesn't deal with overloading between int and
Tom Tromey committed
120 121 122
   * Integer carefully.  If there are two methods, one that takes an
   * Integer and the other taking an int, the method chosen is not
   * specified, and can depend on the order in which the methods are
123
   * declared in the source file.</p>
Tom Tromey committed
124 125
   *
   * @throws Exception if an exception occurs while locating or
126
   *                   invoking the method.
Tom Tromey committed
127 128 129 130 131
   */
  public void execute() throws Exception
  {
    doExecute();
  }
132 133

  private static Class wrappers[] =
Tom Tromey committed
134 135 136 137 138
    {
      Boolean.class, Byte.class, Character.class, Double.class, Float.class,
      Integer.class, Long.class, Short.class
    };

139
  private static Class natives[] =
Tom Tromey committed
140 141 142 143 144
    {
      Boolean.TYPE, Byte.TYPE, Character.TYPE, Double.TYPE, Float.TYPE,
      Integer.TYPE, Long.TYPE, Short.TYPE
    };

145
  /** Given a wrapper class, return the native class for it.
146
   * <p>For example, if <code>c</code> is <code>Integer</code>,
147 148
   * <code>Integer.TYPE</code> is returned.</p>
   */
Tom Tromey committed
149 150 151 152
  private Class unwrap(Class c)
  {
    for (int i = 0; i < wrappers.length; i++)
      if (c == wrappers[i])
153
        return natives[i];
Tom Tromey committed
154 155 156
    return null;
  }

157 158 159 160 161
  /** Returns <code>true</code> if all args can be assigned to
   * <code>params</code>, <code>false</code> otherwise.
   *
   * <p>Arrays are guaranteed to be the same length.</p>
   */
Tom Tromey committed
162 163 164 165
  private boolean compatible(Class[] params, Class[] args)
  {
    for (int i = 0; i < params.length; i++)
      {
166 167 168 169 170
    // Argument types are derived from argument values. If one of them was
    // null then we cannot deduce its type. However null can be assigned to
    // any type.
    if (args[i] == null)
      continue;
171

172
    // Treat Integer like int if appropriate
173 174 175 176 177 178 179 180
        Class nativeType = unwrap(args[i]);
        if (nativeType != null && params[i].isPrimitive()
            && params[i].isAssignableFrom(nativeType))
          continue;
        if (params[i].isAssignableFrom(args[i]))
          continue;

        return false;
Tom Tromey committed
181 182 183 184 185
      }
    return true;
  }

  /**
186 187 188 189
   * Returns <code>true</code> if the method arguments in first are
   * more specific than the method arguments in second, i.e. all
   * arguments in <code>first</code> can be assigned to those in
   * <code>second</code>.
Tom Tromey committed
190
   *
191
   * <p>A method is more specific if all parameters can also be fed to
Tom Tromey committed
192 193
   * the less specific method, because, e.g. the less specific method
   * accepts a base class of the equivalent argument for the more
194
   * specific one.</p>
Tom Tromey committed
195 196 197 198 199 200 201 202 203
   *
   * @param first a <code>Class[]</code> value
   * @param second a <code>Class[]</code> value
   * @return a <code>boolean</code> value
   */
  private boolean moreSpecific(Class[] first, Class[] second)
  {
    for (int j=0; j < first.length; j++)
      {
204 205 206
        if (second[j].isAssignableFrom(first[j]))
          continue;
        return false;
Tom Tromey committed
207 208 209 210 211 212 213
      }
    return true;
  }

  final Object doExecute() throws Exception
  {
    Class klazz = (target instanceof Class)
214
        ? (Class) target : target.getClass();
Tom Tromey committed
215 216
    Object args[] = (arguments == null) ? new Object[0] : arguments;
    Class argTypes[] = new Class[args.length];
217

218 219
    // Retrieve type or use null if the argument is null. The null argument
    // type is later used in compatible().
Tom Tromey committed
220
    for (int i = 0; i < args.length; i++)
221
      argTypes[i] = (args[i] != null) ? args[i].getClass() : null;
Tom Tromey committed
222 223 224

    if (target.getClass().isArray())
      {
225 226 227 228 229 230 231 232 233 234 235 236
        // FIXME: invoke may have to be used.  For now, cast to Number
        // and hope for the best.  If caller didn't behave, we go boom
        // and throw the exception.
        if (methodName.equals("get") && argTypes.length == 1)
          return Array.get(target, ((Number)args[0]).intValue());
        if (methodName.equals("set") && argTypes.length == 2)
          {
            Object obj = Array.get(target, ((Number)args[0]).intValue());
            Array.set(target, ((Number)args[0]).intValue(), args[1]);
            return obj;
          }
        throw new NoSuchMethodException("No matching method for statement " + toString());
Tom Tromey committed
237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255
      }

    // If we already cached the method, just use it.
    if (method != null)
      return method.invoke(target, args);
    else if (ctor != null)
      return ctor.newInstance(args);

    // Find a matching method to call.  JDK seems to go through all
    // this to find the method to call.

    // if method name or length don't match, skip
    // Need to go through each arg
    // If arg is wrapper - check if method arg is matchable builtin
    //  or same type or super
    //  - check that method arg is same or super

    if (methodName.equals("new") && target instanceof Class)
      {
256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283
        Constructor ctors[] = klazz.getConstructors();
        for (int i = 0; i < ctors.length; i++)
          {
            // Skip methods with wrong number of args.
            Class ptypes[] = ctors[i].getParameterTypes();

            if (ptypes.length != args.length)
              continue;

            // Check if method matches
            if (!compatible(ptypes, argTypes))
              continue;

            // Use method[i] if it is more specific.
            // FIXME: should this check both directions and throw if
            // neither is more specific?
            if (ctor == null)
              {
                ctor = ctors[i];
                continue;
              }
            Class mptypes[] = ctor.getParameterTypes();
            if (moreSpecific(ptypes, mptypes))
              ctor = ctors[i];
          }
        if (ctor == null)
          throw new InstantiationException("No matching constructor for statement " + toString());
        return ctor.newInstance(args);
Tom Tromey committed
284 285 286 287 288 289
      }

    Method methods[] = klazz.getMethods();

    for (int i = 0; i < methods.length; i++)
      {
290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311
        // Skip methods with wrong name or number of args.
        if (!methods[i].getName().equals(methodName))
          continue;
        Class ptypes[] = methods[i].getParameterTypes();
        if (ptypes.length != args.length)
          continue;

        // Check if method matches
        if (!compatible(ptypes, argTypes))
          continue;

        // Use method[i] if it is more specific.
        // FIXME: should this check both directions and throw if
        // neither is more specific?
        if (method == null)
          {
            method = methods[i];
            continue;
          }
        Class mptypes[] = method.getParameterTypes();
        if (moreSpecific(ptypes, mptypes))
          method = methods[i];
Tom Tromey committed
312 313 314
      }
    if (method == null)
      throw new NoSuchMethodException("No matching method for statement " + toString());
315 316 317 318 319 320 321 322 323 324 325 326 327 328

    // If we were calling Class.forName(String) we intercept and call the
    // forName-variant that allows a ClassLoader argument. We take the
    // system classloader (aka application classloader) here to make sure
    // that application defined classes can be resolved. If we would not
    // do that the Class.forName implementation would use the class loader
    // of java.beans.Statement which is <null> and cannot resolve application
    // defined classes.
    if (method.equals(
           Class.class.getMethod("forName", new Class[] { String.class })))
      return Class.forName(
               (String) args[0], true, ClassLoader.getSystemClassLoader());

    try {
Tom Tromey committed
329
    return method.invoke(target, args);
330 331
    } catch(IllegalArgumentException iae){
      System.err.println("method: " + method);
332

333 334 335 336 337
      for(int i=0;i<args.length;i++){
        System.err.println("args[" + i + "]: " + args[i]);
      }
      throw iae;
    }
Tom Tromey committed
338 339
  }

340

Tom Tromey committed
341 342 343 344 345 346 347 348 349 350

  /** Return the statement arguments. */
  public Object[] getArguments() { return arguments; }

  /** Return the statement method name. */
  public String getMethodName() { return methodName; }

  /** Return the statement object. */
  public Object getTarget() { return target; }

351 352 353 354
  /**
   * Returns a string representation of this <code>Statement</code>.
   *
   * @return A string representation of this <code>Statement</code>.
355
   */
Tom Tromey committed
356 357
  public String toString()
  {
358
    CPStringBuilder result = new CPStringBuilder();
Tom Tromey committed
359

360 361 362
    String targetName;
    if (target != null)
      targetName = target.getClass().getSimpleName();
363
    else
364
      targetName = "null";
Tom Tromey committed
365

366
    result.append(targetName);
Tom Tromey committed
367 368 369 370
    result.append(".");
    result.append(methodName);
    result.append("(");

Tom Tromey committed
371 372 373
    String sep = "";
    for (int i = 0; i < arguments.length; i++)
      {
Tom Tromey committed
374
        result.append(sep);
375
        result.append(
376
          ( arguments[i] == null ) ? "null" :
377
            ( arguments[i] instanceof String ) ? "\"" + arguments[i] + "\"" :
378
            arguments[i].getClass().getSimpleName());
Tom Tromey committed
379
        sep = ", ";
Tom Tromey committed
380
      }
381
    result.append(");");
Tom Tromey committed
382 383

    return result.toString();
Tom Tromey committed
384
  }
385

Tom Tromey committed
386
}