001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package java.beans;
019:
020: import java.lang.reflect.Array;
021: import java.lang.reflect.Constructor;
022: import java.lang.reflect.InvocationTargetException;
023: import java.lang.reflect.Method;
024: import java.lang.reflect.Modifier;
025: import java.security.PrivilegedAction;
026: import java.util.ArrayList;
027: import java.util.Arrays;
028: import java.util.Comparator;
029: import java.util.HashMap;
030: import java.util.Iterator;
031: import java.util.Map;
032:
033: import org.apache.harmony.beans.internal.nls.Messages;
034:
035: public class Statement {
036: private static final Object[] EMPTY_ARRAY = new Object[0];
037:
038: private Object target;
039:
040: private String methodName;
041:
042: private Object[] arguments;
043:
044: // the special method name donating constructors
045: static final String CONSTRUCTOR_NAME = "new"; //$NON-NLS-1$
046:
047: // the special method name donating array "get"
048: static final String ARRAY_GET = "get"; //$NON-NLS-1$
049:
050: // the special method name donating array "set"
051: static final String ARRAY_SET = "set"; //$NON-NLS-1$
052:
053: public Statement(Object target, String methodName,
054: Object[] arguments) {
055: this .target = target;
056: this .methodName = methodName;
057: if (arguments != null) {
058: this .arguments = arguments;
059: } else {
060: this .arguments = EMPTY_ARRAY;
061: }
062: }
063:
064: @Override
065: public String toString() {
066: StringBuilder sb = new StringBuilder();
067: Object theTarget = getTarget();
068: String theMethodName = getMethodName();
069: Object[] theArguments = getArguments();
070: String targetVar = theTarget != null ? convertClassName(theTarget
071: .getClass())
072: : "null"; //$NON-NLS-1$
073: sb.append(targetVar);
074: sb.append('.');
075: sb.append(theMethodName);
076: sb.append('(');
077: if (theArguments != null) {
078: for (int i = 0; i < theArguments.length; ++i) {
079: if (i > 0) {
080: sb.append(", "); //$NON-NLS-1$
081: }
082: if (theArguments[i] == null) {
083: sb.append("null"); //$NON-NLS-1$
084: } else if (theArguments[i] instanceof String) {
085: sb.append('"');
086: sb.append(theArguments[i].toString());
087: sb.append('"');
088: } else {
089: sb.append(convertClassName(theArguments[i]
090: .getClass()));
091: }
092: }
093: }
094: sb.append(')');
095: sb.append(';');
096: return sb.toString();
097: }
098:
099: public String getMethodName() {
100: return methodName;
101: }
102:
103: public Object[] getArguments() {
104: return arguments;
105: }
106:
107: public Object getTarget() {
108: return target;
109: }
110:
111: public void execute() throws Exception {
112: invokeMethod();
113: }
114:
115: Object invokeMethod() throws Exception {
116: Object result = null;
117: try {
118: Object theTarget = getTarget();
119: String theMethodName = getMethodName();
120: Object[] theArguments = getArguments();
121: if (theTarget.getClass().isArray()) {
122: Method method = findArrayMethod(theMethodName,
123: theArguments);
124: Object[] args = new Object[theArguments.length + 1];
125: args[0] = theTarget;
126: System.arraycopy(theArguments, 0, args, 1,
127: theArguments.length);
128: result = method.invoke(null, args);
129: } else if (theMethodName.equals("newInstance") //$NON-NLS-1$
130: && theTarget == Array.class) {
131: Class<?> componentType = (Class) theArguments[0];
132: int length = ((Integer) theArguments[1]).intValue();
133: result = Array.newInstance(componentType, length);
134: } else if (theMethodName.equals("new") //$NON-NLS-1$
135: || theMethodName.equals("newInstance")) { //$NON-NLS-1$
136: if (theTarget instanceof Class) {
137: Constructor<?> constructor = findConstructor(
138: (Class) theTarget, theArguments);
139: result = constructor.newInstance(theArguments);
140: } else {
141: throw new NoSuchMethodException(this .toString());
142: }
143: } else if (theTarget instanceof Class) {
144: Method method = null;
145: boolean found = false;
146: try {
147: /*
148: * Try to look for a static method of class described by the
149: * given Class object at first process only if the class
150: * differs from Class itself
151: */
152: if (theTarget != Class.class) {
153: method = findMethod((Class) theTarget,
154: theMethodName, theArguments, true);
155: result = method.invoke(null, theArguments);
156: found = true;
157: }
158: } catch (NoSuchMethodException e) {
159: // expected
160: }
161: if (!found) {
162: // static method was not found
163: // try to invoke method of Class object
164: if (theMethodName.equals("forName") //$NON-NLS-1$
165: && theArguments.length == 1
166: && theArguments[0] instanceof String) {
167: // special handling of Class.forName(String)
168: try {
169: result = Class
170: .forName((String) theArguments[0]);
171: } catch (ClassNotFoundException e2) {
172: result = Class.forName(
173: (String) theArguments[0], true,
174: Thread.currentThread()
175: .getContextClassLoader());
176: }
177: } else {
178: method = findMethod(theTarget.getClass(),
179: theMethodName, theArguments, false);
180: result = method.invoke(theTarget, theArguments);
181: }
182: }
183: } else if (theTarget instanceof Iterator) {
184: final Iterator<?> iterator = (Iterator) theTarget;
185: final Method method = findMethod(theTarget.getClass(),
186: theMethodName, theArguments, false);
187: if (iterator.hasNext()) {
188: PrivilegedAction<Object> action = new PrivilegedAction<Object>() {
189:
190: public Object run() {
191: try {
192: method.setAccessible(true);
193: return (method.invoke(iterator,
194: new Object[0]));
195: } catch (Exception e) {
196: // ignore
197: }
198: return null;
199: }
200:
201: };
202: result = action.run();
203: }
204: } else {
205: Method method = findMethod(theTarget.getClass(),
206: theMethodName, theArguments, false);
207: result = method.invoke(theTarget, theArguments);
208: }
209: } catch (InvocationTargetException ite) {
210: Throwable t = ite.getCause();
211: throw (t != null) && (t instanceof Exception) ? (Exception) t
212: : ite;
213: }
214: return result;
215: }
216:
217: private Method findArrayMethod(String theMethodName,
218: Object[] theArguments) throws NoSuchMethodException {
219: // the code below reproduces exact RI exception throwing behavior
220: if (!theMethodName.equals("set") && !theMethodName.equals("get")) { //$NON-NLS-1$ //$NON-NLS-2$
221: throw new NoSuchMethodException(Messages
222: .getString("beans.3C")); //$NON-NLS-1$
223: } else if (theArguments.length > 0
224: && theArguments[0].getClass() != Integer.class) {
225: throw new ClassCastException(Messages.getString("beans.3D")); //$NON-NLS-1$
226: } else if (theMethodName.equals("get") && (theArguments.length != 1)) { //$NON-NLS-1$
227: throw new ArrayIndexOutOfBoundsException(Messages
228: .getString("beans.3E")); //$NON-NLS-1$
229: } else if (theMethodName.equals("set") && (theArguments.length != 2)) { //$NON-NLS-1$
230: throw new ArrayIndexOutOfBoundsException(Messages
231: .getString("beans.3F")); //$NON-NLS-1$
232: }
233: if (theMethodName.equals("get")) { //$NON-NLS-1$
234: return Array.class.getMethod(
235: "get", new Class[] { Object.class, //$NON-NLS-1$
236: int.class });
237: }
238: return Array.class.getMethod("set", new Class[] { Object.class, //$NON-NLS-1$
239: int.class, Object.class });
240: }
241:
242: private Constructor<?> findConstructor(Class<?> targetClass,
243: Object[] theArguments) throws NoSuchMethodException {
244: Class<?>[] argClasses = getClasses(theArguments);
245: Constructor<?> result = null;
246: Constructor<?>[] constructors = targetClass.getConstructors();
247: for (Constructor<?> constructor : constructors) {
248: Class<?>[] parameterTypes = constructor.getParameterTypes();
249: if (parameterTypes.length == argClasses.length) {
250: boolean found = true;
251: for (int j = 0; j < parameterTypes.length; ++j) {
252: boolean argIsNull = argClasses[j] == null;
253: boolean argIsPrimitiveWrapper = isPrimitiveWrapper(
254: argClasses[j], parameterTypes[j]);
255: boolean paramIsPrimitive = parameterTypes[j]
256: .isPrimitive();
257: boolean paramIsAssignable = argIsNull ? false
258: : parameterTypes[j]
259: .isAssignableFrom(argClasses[j]);
260: if (!argIsNull && !paramIsAssignable
261: && !argIsPrimitiveWrapper || argIsNull
262: && paramIsPrimitive) {
263: found = false;
264: break;
265: }
266: }
267: if (found) {
268: result = constructor;
269: break;
270: }
271: }
272: }
273: if (result == null) {
274: throw new NoSuchMethodException(Messages.getString(
275: "beans.40", targetClass.getName())); //$NON-NLS-1$
276: }
277: return result;
278: }
279:
280: /**
281: * Searches for best matching method for given name and argument types.
282: */
283: static Method findMethod(Class<?> targetClass, String methodName,
284: Object[] arguments, boolean methodIsStatic)
285: throws NoSuchMethodException {
286: Class<?>[] argClasses = getClasses(arguments);
287: Method[] methods = targetClass.getMethods();
288: ArrayList<Method> foundMethods = new ArrayList<Method>();
289: Method[] foundMethodsArr;
290: for (Method method : methods) {
291: int mods = method.getModifiers();
292: if (method.getName().equals(methodName)
293: && (methodIsStatic ? Modifier.isStatic(mods) : true)) {
294: Class<?>[] parameterTypes = method.getParameterTypes();
295: if (parameterTypes.length == argClasses.length) {
296: boolean found = true;
297: for (int j = 0; j < parameterTypes.length; ++j) {
298: boolean argIsNull = (argClasses[j] == null);
299: boolean argIsPrimitiveWrapper = isPrimitiveWrapper(
300: argClasses[j], parameterTypes[j]);
301: boolean paramIsAssignable = argIsNull ? false
302: : parameterTypes[j]
303: .isAssignableFrom(argClasses[j]);
304: if (!argIsNull && !paramIsAssignable
305: && !argIsPrimitiveWrapper) {
306: found = false;
307: break;
308: }
309: }
310: if (found) {
311: foundMethods.add(method);
312: }
313: }
314: }
315: }
316: if (foundMethods.size() == 0) {
317: throw new NoSuchMethodException(Messages.getString(
318: "beans.41", methodName)); //$NON-NLS-1$
319: }
320: if (foundMethods.size() == 1) {
321: return foundMethods.get(0);
322: }
323: foundMethodsArr = foundMethods.toArray(new Method[foundMethods
324: .size()]);
325: //find the most relevant one
326: MethodComparator comparator = new MethodComparator(methodName,
327: argClasses);
328: Method chosenOne = foundMethodsArr[0];
329: for (int i = 1; i < foundMethodsArr.length; i++) {
330: int difference = comparator.compare(chosenOne,
331: foundMethodsArr[i]);
332: //if 2 methods have same relevance, throw exception
333: if (difference == 0) {
334: throw new NoSuchMethodException(
335: "Cannot decide which method to call: " + methodName); //$NON-NLS-1$
336: }
337: if (difference > 0) {
338: chosenOne = foundMethodsArr[i];
339: }
340: }
341: return chosenOne;
342: }
343:
344: static boolean isStaticMethodCall(Statement stmt) {
345: Object target = stmt.getTarget();
346: String mName = stmt.getMethodName();
347: if (!(target instanceof Class)) {
348: return false;
349: }
350: try {
351: Statement.findMethod((Class) target, mName, stmt
352: .getArguments(), true);
353: return true;
354: } catch (NoSuchMethodException e) {
355: return false;
356: }
357: }
358:
359: /*
360: * The list of "method signatures" used by persistence delegates to create
361: * objects. Not necessary reflects to real methods.
362: */
363: private static final String[][] pdConstructorSignatures = {
364: { "java.lang.Class", "new", "java.lang.Boolean", "", "", "" }, //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$
365: { "java.lang.Class", "new", "java.lang.Byte", "", "", "" }, //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$
366: {
367: "java.lang.Class", "new", "java.lang.Character", "", "", "" }, //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$
368: { "java.lang.Class", "new", "java.lang.Double", "", "", "" }, //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$
369: { "java.lang.Class", "new", "java.lang.Float", "", "", "" }, //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$
370: { "java.lang.Class", "new", "java.lang.Integer", "", "", "" }, //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$
371: { "java.lang.Class", "new", "java.lang.Long", "", "", "" }, //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$
372: { "java.lang.Class", "new", "java.lang.Short", "", "", "" }, //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$
373: { "java.lang.Class", "new", "java.lang.String", "", "", "" }, //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$
374: {
375: "java.lang.Class", "forName", "java.lang.String", "", "", "" }, //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$
376: { "java.lang.Class", "newInstance", "java.lang.Class", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
377: "java.lang.Integer", "", "" }, //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
378: { "java.lang.reflect.Field", "get", "null", "", "", "" }, //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$
379: {
380: "java.lang.Class", "forName", "java.lang.String", "", "", "" } //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$
381: };
382:
383: static boolean isPDConstructor(Statement stmt) {
384: Object target = stmt.getTarget();
385: String methodName = stmt.getMethodName();
386: Object[] args = stmt.getArguments();
387: String[] sig = new String[pdConstructorSignatures[0].length];
388: if (target == null || methodName == null || args == null
389: || args.length == 0) {
390: // not a constructor for sure
391: return false;
392: }
393: sig[0] = target.getClass().getName();
394: sig[1] = methodName;
395: for (int i = 2; i < sig.length; i++) {
396: if (args.length > i - 2) {
397: sig[i] = args[i - 2] != null ? args[i - 2].getClass()
398: .getName() : "null"; //$NON-NLS-1$
399: } else {
400: sig[i] = ""; //$NON-NLS-1$
401: }
402: }
403: for (String[] element : pdConstructorSignatures) {
404: if (Arrays.equals(sig, element)) {
405: return true;
406: }
407: }
408: return false;
409: }
410:
411: private static boolean isPrimitiveWrapper(Class<?> wrapper,
412: Class<?> base) {
413: return (base == boolean.class) && (wrapper == Boolean.class)
414: || (base == byte.class) && (wrapper == Byte.class)
415: || (base == char.class) && (wrapper == Character.class)
416: || (base == short.class) && (wrapper == Short.class)
417: || (base == int.class) && (wrapper == Integer.class)
418: || (base == long.class) && (wrapper == Long.class)
419: || (base == float.class) && (wrapper == Float.class)
420: || (base == double.class) && (wrapper == Double.class);
421: }
422:
423: private static Class<?> getPrimitiveWrapper(Class<?> base) {
424: Class<?> res = null;
425: if (base == boolean.class) {
426: res = Boolean.class;
427: } else if (base == byte.class) {
428: res = Byte.class;
429: } else if (base == char.class) {
430: res = Character.class;
431: } else if (base == short.class) {
432: res = Short.class;
433: } else if (base == int.class) {
434: res = Integer.class;
435: } else if (base == long.class) {
436: res = Long.class;
437: } else if (base == float.class) {
438: res = Float.class;
439: } else if (base == double.class) {
440: res = Double.class;
441: }
442: return res;
443: }
444:
445: static String convertClassName(Class<?> type) {
446: Class<?> componentType = type.getComponentType();
447: Class<?> resultType = (componentType == null) ? type
448: : componentType;
449: String result = resultType.getName();
450: int k = result.lastIndexOf('.');
451: if (k != -1 && k < result.length()) {
452: result = result.substring(k + 1);
453: }
454: if (componentType != null) {
455: result += "Array"; //$NON-NLS-1$
456: }
457: return result;
458: }
459:
460: private static Class<?>[] getClasses(Object[] arguments) {
461: Class<?>[] result = new Class[arguments.length];
462: for (int i = 0; i < arguments.length; ++i) {
463: result[i] = (arguments[i] == null) ? null : arguments[i]
464: .getClass();
465: }
466: return result;
467: }
468:
469: /**
470: * Comparator to determine which of two methods is "closer" to the reference
471: * method.
472: */
473: static class MethodComparator implements Comparator<Method> {
474: static int INFINITY = Integer.MAX_VALUE;
475:
476: private String referenceMethodName;
477:
478: private Class<?>[] referenceMethodArgumentTypes;
479:
480: private final Map<Method, Integer> cache;
481:
482: public MethodComparator(String refMethodName,
483: Class<?>[] refArgumentTypes) {
484: this .referenceMethodName = refMethodName;
485: this .referenceMethodArgumentTypes = refArgumentTypes;
486: cache = new HashMap<Method, Integer>();
487: }
488:
489: public int compare(Method m1, Method m2) {
490: Integer norm1 = cache.get(m1);
491: Integer norm2 = cache.get(m2);
492: if (norm1 == null) {
493: norm1 = Integer.valueOf(getNorm(m1));
494: cache.put(m1, norm1);
495: }
496: if (norm2 == null) {
497: norm2 = Integer.valueOf(getNorm(m2));
498: cache.put(m2, norm2);
499: }
500: return (norm1.intValue() - norm2.intValue());
501: }
502:
503: /**
504: * Returns the norm for given method. The norm is the "distance" from
505: * the reference method to the given method.
506: *
507: * @param m
508: * the method to calculate the norm for
509: * @return norm of given method
510: */
511: private int getNorm(Method m) {
512: String methodName = m.getName();
513: Class<?>[] argumentTypes = m.getParameterTypes();
514: int totalNorm = 0;
515: if (!referenceMethodName.equals(methodName)
516: || referenceMethodArgumentTypes.length != argumentTypes.length) {
517: return INFINITY;
518: }
519: for (int i = 0; i < referenceMethodArgumentTypes.length; i++) {
520: if (referenceMethodArgumentTypes[i] == null) {
521: if (argumentTypes[i].isPrimitive()) {
522: return INFINITY;
523: }
524: // doesn't affect the norm calculation if null
525: continue;
526: }
527: if (referenceMethodArgumentTypes[i].isPrimitive()) {
528: referenceMethodArgumentTypes[i] = getPrimitiveWrapper(referenceMethodArgumentTypes[i]);
529: }
530: if (argumentTypes[i].isPrimitive()) {
531: argumentTypes[i] = getPrimitiveWrapper(argumentTypes[i]);
532: }
533: totalNorm += getDistance(
534: referenceMethodArgumentTypes[i],
535: argumentTypes[i]);
536: }
537: return totalNorm;
538: }
539:
540: /**
541: * Returns a "hierarchy distance" between two classes.
542: *
543: * @param clz1
544: * @param clz2
545: * should be superclass or superinterface of clz1
546: * @return hierarchy distance from clz1 to clz2, Integer.MAX_VALUE if
547: * clz2 is not assignable from clz1.
548: */
549: private static int getDistance(Class<?> clz1, Class<?> clz2) {
550: Class<?> super Clz;
551: int super Dist = INFINITY;
552: if (!clz2.isAssignableFrom(clz1)) {
553: return INFINITY;
554: }
555: if (clz1.getName().equals(clz2.getName())) {
556: return 0;
557: }
558: super Clz = clz1.getSuperclass();
559: if (super Clz != null) {
560: super Dist = getDistance(super Clz, clz2);
561: }
562: if (clz2.isInterface()) {
563: Class<?>[] interfaces = clz1.getInterfaces();
564: int bestDist = INFINITY;
565: for (Class<?> element : interfaces) {
566: int curDist = getDistance(element, clz2);
567: if (curDist < bestDist) {
568: bestDist = curDist;
569: }
570: }
571: if (super Dist < bestDist) {
572: bestDist = super Dist;
573: }
574: return (bestDist != INFINITY ? bestDist + 1 : INFINITY);
575: }
576: return (super Dist != INFINITY ? super Dist + 1 : INFINITY);
577: }
578: }
579: }
|