Source for java.beans.Statement

   1: /* java.beans.Statement
   2:    Copyright (C) 2004, 2005 Free Software Foundation, Inc.
   3: 
   4: This file is part of GNU Classpath.
   5: 
   6: GNU Classpath is free software; you can redistribute it and/or modify
   7: it under the terms of the GNU General Public License as published by
   8: the Free Software Foundation; either version 2, or (at your option)
   9: any later version.
  10:  
  11: GNU Classpath is distributed in the hope that it will be useful, but
  12: WITHOUT ANY WARRANTY; without even the implied warranty of
  13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14: General Public License for more details.
  15: 
  16: You should have received a copy of the GNU General Public License
  17: along with GNU Classpath; see the file COPYING.  If not, write to the
  18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  19: 02110-1301 USA.
  20: 
  21: Linking this library statically or dynamically with other modules is
  22: making a combined work based on this library.  Thus, the terms and
  23: conditions of the GNU General Public License cover the whole
  24: combination.
  25: 
  26: As a special exception, the copyright holders of this library give you
  27: permission to link this library with independent modules to produce an
  28: executable, regardless of the license terms of these independent
  29: modules, and to copy and distribute the resulting executable under
  30: terms of your choice, provided that you also meet, for each linked
  31: independent module, the terms and conditions of the license of that
  32: module.  An independent module is a module which is not derived from
  33: or based on this library.  If you modify this library, you may extend
  34: this exception to your version of the library, but you are not
  35: obligated to do so.  If you do not wish to do so, delete this
  36: exception statement from your version. */
  37: 
  38: 
  39: package java.beans;
  40: 
  41: import java.lang.reflect.Array;
  42: import java.lang.reflect.Constructor;
  43: import java.lang.reflect.Method;
  44: 
  45: import java.util.HashMap;
  46: import java.util.WeakHashMap;
  47: 
  48: /**
  49:  * class Statement
  50:  *
  51:  * A Statement captures the execution of an object method.  It stores
  52:  * the object, the method to call, and the arguments to the method and
  53:  * provides the ability to execute the method on the object, using the
  54:  * provided arguments.
  55:  *
  56:  * @since 1.4
  57:  */
  58: public class Statement
  59: {
  60:   /** Nested map for the relation between a class, its instances and their
  61:     * names.
  62:     */
  63:   private static HashMap classMaps = new HashMap();
  64: 
  65:   private Object target;
  66:   private String methodName;
  67:   private Object[] arguments;
  68: 
  69:   // One or the other of these will get a value after execute is
  70:   // called once, but not both.
  71:   private transient Method method;
  72:   private transient Constructor ctor;
  73: 
  74:   /**
  75:    * <p>Constructs a statement representing the invocation of
  76:    * object.methodName(arg[0], arg[1], ...);</p>
  77:    *
  78:    * <p>If the argument array is null it is replaced with an
  79:    * array of zero length.</p>
  80:    *
  81:    * @param target The object to invoke the method on.
  82:    * @param methodName The object method to invoke.
  83:    * @param arguments An array of arguments to pass to the method.
  84:    */
  85:   public Statement(Object target, String methodName, Object[] arguments)
  86:   {
  87:     this.target = target;
  88:     this.methodName = methodName;
  89:     this.arguments = (arguments != null) ? arguments : new Object[0];
  90:     storeTargetName(target);
  91:   }
  92: 
  93:   /** Creates a name for the target instance or does nothing if the object's
  94:    * name is already known. This makes sure that there *is* a name for every
  95:    * target instance.
  96:    */
  97:   private static synchronized void storeTargetName(Object obj)
  98:   {
  99:     Class klass = obj.getClass();
 100:     WeakHashMap names = (WeakHashMap) classMaps.get(klass);
 101: 
 102:     if ( names == null )
 103:     {
 104:       names = new WeakHashMap();
 105: 
 106:       names.put(obj,
 107:         ( klass == String.class ? ("\"" + obj + "\"") :
 108:         (klass.getName() + names.size()) ));
 109: 
 110:       classMaps.put(klass, names);
 111: 
 112:       return;
 113:     }
 114: 
 115:     String targetName = (String) names.get(obj);
 116:     if ( targetName == null )
 117:     {
 118:       names.put(obj,
 119:         ( klass == String.class ? ("\"" + obj + "\"") :
 120:         (klass.getName() + names.size()) ));
 121:     }
 122: 
 123:     // Nothing to do. The given object was already stored.
 124:   }
 125: 
 126:   /**
 127:    * Execute the statement.
 128:    *
 129:    * Finds the specified method in the target object and calls it with
 130:    * the arguments given in the constructor.
 131:    *
 132:    * The most specific method according to the JLS(15.11) is used when
 133:    * there are multiple methods with the same name.
 134:    *
 135:    * Execute performs some special handling for methods and
 136:    * parameters:
 137:    *
 138:    * Static methods can be executed by providing the class as a
 139:    * target.
 140:    *
 141:    * The method name new is reserved to call the constructor 
 142:    * new() will construct an object and return it.  Not useful unless
 143:    * an expression :-)
 144:    *
 145:    * If the target is an array, get and set as defined in
 146:    * java.util.List are recognized as valid methods and mapped to the
 147:    * methods of the same name in java.lang.reflect.Array.
 148:    *
 149:    * The native datatype wrappers Boolean, Byte, Character, Double,
 150:    * Float, Integer, Long, and Short will map to methods that have
 151:    * native datatypes as parameters, in the same way as Method.invoke.
 152:    * However, these wrappers also select methods that actually take
 153:    * the wrapper type as an argument.
 154:    *
 155:    * The Sun spec doesn't deal with overloading between int and
 156:    * Integer carefully.  If there are two methods, one that takes an
 157:    * Integer and the other taking an int, the method chosen is not
 158:    * specified, and can depend on the order in which the methods are
 159:    * declared in the source file.
 160:    *
 161:    * @throws Exception if an exception occurs while locating or
 162:    *                invoking the method.
 163:    */
 164:   public void execute() throws Exception
 165:   {
 166:     doExecute();
 167:   }
 168:   
 169:   private static Class wrappers[] = 
 170:     {
 171:       Boolean.class, Byte.class, Character.class, Double.class, Float.class,
 172:       Integer.class, Long.class, Short.class
 173:     };
 174: 
 175:   private static Class natives[] = 
 176:     {
 177:       Boolean.TYPE, Byte.TYPE, Character.TYPE, Double.TYPE, Float.TYPE,
 178:       Integer.TYPE, Long.TYPE, Short.TYPE
 179:     };
 180: 
 181:   // Given a wrapper class, return the native class for it.  For
 182:   // example, if c is Integer, Integer.TYPE is returned.
 183:   private Class unwrap(Class c)
 184:   {
 185:     for (int i = 0; i < wrappers.length; i++)
 186:       if (c == wrappers[i])
 187:     return natives[i];
 188:     return null;
 189:   }
 190: 
 191:   // Return true if all args can be assigned to params, false
 192:   // otherwise.  Arrays are guaranteed to be the same length.
 193:   private boolean compatible(Class[] params, Class[] args)
 194:   {
 195:     for (int i = 0; i < params.length; i++)
 196:       {
 197:     // Treat Integer like int if appropriate
 198:     Class nativeType = unwrap(args[i]);
 199:     if (nativeType != null && params[i].isPrimitive()
 200:         && params[i].isAssignableFrom(nativeType))
 201:       continue;
 202:     if (params[i].isAssignableFrom(args[i]))
 203:       continue;
 204: 
 205:     return false;
 206:       }
 207:     return true;
 208:   }
 209: 
 210:   /**
 211:    * Return true if the method arguments in first are more specific
 212:    * than the method arguments in second, i.e. all args in first can
 213:    * be assigned to those in second.
 214:    *
 215:    * A method is more specific if all parameters can also be fed to
 216:    * the less specific method, because, e.g. the less specific method
 217:    * accepts a base class of the equivalent argument for the more
 218:    * specific one.
 219:    *
 220:    * @param first a <code>Class[]</code> value
 221:    * @param second a <code>Class[]</code> value
 222:    * @return a <code>boolean</code> value
 223:    */
 224:   private boolean moreSpecific(Class[] first, Class[] second)
 225:   {
 226:     for (int j=0; j < first.length; j++)
 227:       {
 228:     if (second[j].isAssignableFrom(first[j]))
 229:       continue;
 230:     return false;
 231:       }
 232:     return true;
 233:   }
 234: 
 235:   final Object doExecute() throws Exception
 236:   {
 237:     Class klazz = (target instanceof Class)
 238:     ? (Class) target : target.getClass();
 239:     Object args[] = (arguments == null) ? new Object[0] : arguments;
 240:     Class argTypes[] = new Class[args.length];
 241:     for (int i = 0; i < args.length; i++)
 242:       argTypes[i] = args[i].getClass();
 243: 
 244:     if (target.getClass().isArray())
 245:       {
 246:     // FIXME: invoke may have to be used.  For now, cast to Number
 247:     // and hope for the best.  If caller didn't behave, we go boom
 248:     // and throw the exception.
 249:     if (methodName.equals("get") && argTypes.length == 1)
 250:       return Array.get(target, ((Number)args[0]).intValue());
 251:     if (methodName.equals("set") && argTypes.length == 2)
 252:       {
 253:         Object obj = Array.get(target, ((Number)args[0]).intValue());
 254:         Array.set(target, ((Number)args[0]).intValue(), args[1]);
 255:         return obj;
 256:       }
 257:     throw new NoSuchMethodException("No matching method for statement " + toString());
 258:       }
 259: 
 260:     // If we already cached the method, just use it.
 261:     if (method != null)
 262:       return method.invoke(target, args);
 263:     else if (ctor != null)
 264:       return ctor.newInstance(args);
 265: 
 266:     // Find a matching method to call.  JDK seems to go through all
 267:     // this to find the method to call.
 268: 
 269:     // if method name or length don't match, skip
 270:     // Need to go through each arg
 271:     // If arg is wrapper - check if method arg is matchable builtin
 272:     //  or same type or super
 273:     //  - check that method arg is same or super
 274: 
 275:     if (methodName.equals("new") && target instanceof Class)
 276:       {
 277:     Constructor ctors[] = klazz.getConstructors();
 278:     for (int i = 0; i < ctors.length; i++)
 279:       {
 280:         // Skip methods with wrong number of args.
 281:         Class ptypes[] = ctors[i].getParameterTypes();
 282: 
 283:         if (ptypes.length != args.length)
 284:           continue;
 285: 
 286:         // Check if method matches
 287:         if (!compatible(ptypes, argTypes))
 288:           continue;
 289: 
 290:         // Use method[i] if it is more specific. 
 291:         // FIXME: should this check both directions and throw if
 292:         // neither is more specific?
 293:         if (ctor == null)
 294:           {
 295:         ctor = ctors[i];
 296:         continue;
 297:           }
 298:         Class mptypes[] = ctor.getParameterTypes();
 299:         if (moreSpecific(ptypes, mptypes))
 300:           ctor = ctors[i];
 301:       }
 302:     if (ctor == null)
 303:       throw new InstantiationException("No matching constructor for statement " + toString());
 304:     return ctor.newInstance(args);
 305:       }
 306: 
 307:     Method methods[] = klazz.getMethods();
 308: 
 309:     for (int i = 0; i < methods.length; i++)
 310:       {
 311:     // Skip methods with wrong name or number of args.
 312:     if (!methods[i].getName().equals(methodName))
 313:       continue;
 314:     Class ptypes[] = methods[i].getParameterTypes();
 315:     if (ptypes.length != args.length)
 316:       continue;
 317: 
 318:     // Check if method matches
 319:     if (!compatible(ptypes, argTypes))
 320:       continue;
 321: 
 322:     // Use method[i] if it is more specific. 
 323:     // FIXME: should this check both directions and throw if
 324:     // neither is more specific?
 325:     if (method == null)
 326:       {
 327:         method = methods[i];
 328:         continue;
 329:       }
 330:     Class mptypes[] = method.getParameterTypes();
 331:     if (moreSpecific(ptypes, mptypes))
 332:       method = methods[i];
 333:       }
 334:     if (method == null)
 335:       throw new NoSuchMethodException("No matching method for statement " + toString());
 336:     return method.invoke(target, args);
 337:   }
 338: 
 339:   
 340: 
 341:   /** Return the statement arguments. */
 342:   public Object[] getArguments() { return arguments; }
 343: 
 344:   /** Return the statement method name. */
 345:   public String getMethodName() { return methodName; }
 346: 
 347:   /** Return the statement object. */
 348:   public Object getTarget() { return target; }
 349: 
 350:   /** Return a string representation. */
 351:   public String toString()
 352:   {
 353:     StringBuffer result = new StringBuffer(); 
 354: 
 355:     Class klass = target.getClass();
 356: 
 357:     result.append( ((WeakHashMap) classMaps.get(klass)).get(target));
 358:     result.append(".");
 359:     result.append(methodName);
 360:     result.append("(");
 361: 
 362:     String sep = "";
 363:     for (int i = 0; i < arguments.length; i++)
 364:       {
 365:         result.append(sep);
 366:         result.append(arguments[i].getClass().getName());
 367:         sep = ", ";
 368:       }
 369:     result.append(")");
 370: 
 371:     return result.toString();
 372:   }
 373: }