001: /*
002: * Copyright (c) 2003 The Visigoth Software Society. All rights
003: * reserved.
004: *
005: * Redistribution and use in source and binary forms, with or without
006: * modification, are permitted provided that the following conditions
007: * are met:
008: *
009: * 1. Redistributions of source code must retain the above copyright
010: * notice, this list of conditions and the following disclaimer.
011: *
012: * 2. Redistributions in binary form must reproduce the above copyright
013: * notice, this list of conditions and the following disclaimer in
014: * the documentation and/or other materials provided with the
015: * distribution.
016: *
017: * 3. The end-user documentation included with the redistribution, if
018: * any, must include the following acknowledgement:
019: * "This product includes software developed by the
020: * Visigoth Software Society (http://www.visigoths.org/)."
021: * Alternately, this acknowledgement may appear in the software itself,
022: * if and wherever such third-party acknowledgements normally appear.
023: *
024: * 4. Neither the name "FreeMarker", "Visigoth", nor any of the names of the
025: * project contributors may be used to endorse or promote products derived
026: * from this software without prior written permission. For written
027: * permission, please contact visigoths@visigoths.org.
028: *
029: * 5. Products derived from this software may not be called "FreeMarker" or "Visigoth"
030: * nor may "FreeMarker" or "Visigoth" appear in their names
031: * without prior written permission of the Visigoth Software Society.
032: *
033: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
034: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
035: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
036: * DISCLAIMED. IN NO EVENT SHALL THE VISIGOTH SOFTWARE SOCIETY OR
037: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
038: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
039: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
040: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
041: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
042: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
043: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
044: * SUCH DAMAGE.
045: * ====================================================================
046: *
047: * This software consists of voluntary contributions made by many
048: * individuals on behalf of the Visigoth Software Society. For more
049: * information on the Visigoth Software Society, please see
050: * http://www.visigoths.org/
051: */
052:
053: package freemarker.ext.beans;
054:
055: import java.lang.reflect.AccessibleObject;
056: import java.lang.reflect.Constructor;
057: import java.lang.reflect.Method;
058: import java.util.ArrayList;
059: import java.util.HashMap;
060: import java.util.HashSet;
061: import java.util.Iterator;
062: import java.util.LinkedList;
063: import java.util.List;
064: import java.util.Map;
065: import java.util.Set;
066:
067: import freemarker.template.TemplateModelException;
068:
069: class MethodMap {
070: private static final Class BIGDECIMAL_CLASS = java.math.BigDecimal.class;
071: private static final Class NUMBER_CLASS = java.lang.Number.class;
072:
073: private static final Object[] EMPTY_ARGS = new Object[0];
074: private static final Class OBJECT_CLASS = java.lang.Object.class;
075: private static final ClassString EMPTY_STRING = new ClassString(
076: EMPTY_ARGS);
077:
078: private static final Object NO_SUCH_METHOD = new Object();
079: private static final Object AMBIGUOUS_METHOD = new Object();
080:
081: private final String name;
082: // Cache of Class[] --> AccessibleObject. Maps the actual types involved in
083: // a method/constructor call to the most specific method/constructor for
084: // those types
085: private final Map selectorCache = new HashMap();
086: private Class[][] unwrapTypes;
087: private final List methods = new LinkedList();
088:
089: MethodMap(String name) {
090: this .name = name;
091: }
092:
093: void addMethod(Method method) {
094: methods.add(method);
095: updateUnwrapTypes(method.getParameterTypes());
096: }
097:
098: void addConstructor(Constructor constructor) {
099: methods.add(constructor);
100: updateUnwrapTypes(constructor.getParameterTypes());
101: }
102:
103: private void updateUnwrapTypes(Class[] argTypes) {
104: int l = argTypes.length - 1;
105: if (l == -1) {
106: return;
107: }
108: if (unwrapTypes == null) {
109: unwrapTypes = new Class[l + 1][];
110: unwrapTypes[l] = argTypes;
111: } else if (unwrapTypes.length <= l) {
112: Class[][] newUnwrapTypes = new Class[l + 1][];
113: System.arraycopy(unwrapTypes, 0, newUnwrapTypes, 0,
114: unwrapTypes.length);
115: unwrapTypes = newUnwrapTypes;
116: unwrapTypes[l] = argTypes;
117: } else {
118: Class[] oldTypes = unwrapTypes[l];
119: if (oldTypes == null) {
120: unwrapTypes[l] = argTypes;
121: } else {
122: for (int i = 0; i < oldTypes.length; ++i) {
123: oldTypes[i] = getMostSpecificCommonType(
124: oldTypes[i], argTypes[i]);
125: }
126: }
127: }
128: }
129:
130: String getName() {
131: return name;
132: }
133:
134: Class[] getUnwrapTypes(List args) throws TemplateModelException {
135: int l = args.size() - 1;
136: if (l == -1) {
137: return EMPTY_STRING.getClasses();
138: }
139: if (l < unwrapTypes.length) {
140: Class[] retval = unwrapTypes[l];
141: if (retval != null) {
142: return retval;
143: }
144: }
145: throw new TemplateModelException("No signature of method "
146: + name + " accepts " + (l + 1) + " arguments");
147: }
148:
149: AccessibleObject getMostSpecific(final Object[] args)
150: throws TemplateModelException {
151: ClassString cs = null;
152: if (args == null) {
153: cs = EMPTY_STRING;
154: } else {
155: cs = new ClassString(args);
156: }
157: synchronized (selectorCache) {
158: Object obj = selectorCache.get(cs);
159: if (obj == null) {
160: selectorCache
161: .put(cs, obj = cs.getMostSpecific(methods));
162: }
163: if (obj instanceof AccessibleObject) {
164: return (AccessibleObject) obj;
165: }
166: if (obj == NO_SUCH_METHOD) {
167: throw new TemplateModelException(
168: "No signature of method " + name + " matches "
169: + cs.listArgumentTypes());
170: } else {
171: // Can be only AMBIGUOUS_METHOD
172: throw new TemplateModelException(
173: "Multiple signatures of method " + name
174: + " match " + cs.listArgumentTypes());
175: }
176: }
177: }
178:
179: private static final class ClassString {
180: private final Class[] classes;
181:
182: ClassString(Object[] objects) {
183: int l = objects.length;
184: classes = new Class[l];
185: for (int i = 0; i < l; ++i) {
186: Object obj = objects[i];
187: classes[i] = obj == null ? OBJECT_CLASS : obj
188: .getClass();
189: }
190: }
191:
192: Class[] getClasses() {
193: return classes;
194: }
195:
196: public int hashCode() {
197: int hash = 0;
198: for (int i = 0; i < classes.length; ++i) {
199: hash ^= classes[i].hashCode();
200: }
201: return hash;
202: }
203:
204: public boolean equals(Object o) {
205: if (o instanceof ClassString) {
206: ClassString cs = (ClassString) o;
207: if (cs.classes.length != classes.length) {
208: return false;
209: }
210: for (int i = 0; i < classes.length; ++i) {
211: if (cs.classes[i] != classes[i]) {
212: return false;
213: }
214: }
215: return true;
216: }
217: return false;
218: }
219:
220: private static final int MORE_SPECIFIC = 0;
221: private static final int LESS_SPECIFIC = 1;
222: private static final int INDETERMINATE = 2;
223:
224: Object getMostSpecific(List methods) {
225: LinkedList applicables = getApplicables(methods);
226: if (applicables.isEmpty()) {
227: return NO_SUCH_METHOD;
228: }
229: if (applicables.size() == 1) {
230: return applicables.getFirst();
231: }
232: LinkedList maximals = new LinkedList();
233: for (Iterator applicable = applicables.iterator(); applicable
234: .hasNext();) {
235: Object objapp = applicable.next();
236: Class[] appArgs = getParameterTypes(objapp);
237: boolean lessSpecific = false;
238: for (Iterator maximal = maximals.iterator(); !lessSpecific
239: && maximal.hasNext();) {
240: Object max = maximal.next();
241: switch (moreSpecific(appArgs,
242: getParameterTypes(max))) {
243: case MORE_SPECIFIC: {
244: maximal.remove();
245: break;
246: }
247: case LESS_SPECIFIC: {
248: lessSpecific = true;
249: break;
250: }
251: }
252: }
253: if (!lessSpecific) {
254: maximals.addLast(objapp);
255: }
256: }
257: if (maximals.size() > 1) {
258: return AMBIGUOUS_METHOD;
259: }
260: return maximals.getFirst();
261: }
262:
263: private static Class[] getParameterTypes(Object obj) {
264: if (obj instanceof Method) {
265: return ((Method) obj).getParameterTypes();
266: }
267: if (obj instanceof Constructor) {
268: return ((Constructor) obj).getParameterTypes();
269: }
270: // Cannot happen
271: throw new Error();
272: }
273:
274: private static int moreSpecific(Class[] c1, Class[] c2) {
275: boolean c1MoreSpecific = false;
276: boolean c2MoreSpecific = false;
277: for (int i = 0; i < c1.length; ++i) {
278: if (c1[i] != c2[i]) {
279: c1MoreSpecific = c1MoreSpecific
280: || isMoreSpecific(c1[i], c2[i]);
281: c2MoreSpecific = c2MoreSpecific
282: || isMoreSpecific(c2[i], c1[i]);
283: }
284: }
285: if (c1MoreSpecific) {
286: if (c2MoreSpecific) {
287: return INDETERMINATE;
288: }
289: return MORE_SPECIFIC;
290: }
291: if (c2MoreSpecific) {
292: return LESS_SPECIFIC;
293: }
294: return INDETERMINATE;
295: }
296:
297: /**
298: * Returns all methods that are applicable to actual
299: * parameter classes represented by this ClassString object.
300: */
301: LinkedList getApplicables(List methods) {
302: LinkedList list = new LinkedList();
303: for (Iterator imethod = methods.iterator(); imethod
304: .hasNext();) {
305: Object method = imethod.next();
306: if (isApplicable(method)) {
307: list.add(method);
308: }
309:
310: }
311: return list;
312: }
313:
314: /**
315: * Returns true if the supplied method is applicable to actual
316: * parameter classes represented by this ClassString object.
317: *
318: */
319: private boolean isApplicable(Object method) {
320: Class[] methodArgs = getParameterTypes(method);
321: if (methodArgs.length != classes.length) {
322: return false;
323: }
324: for (int i = 0; i < classes.length; ++i) {
325: if (!isMethodInvocationConvertible(methodArgs[i],
326: classes[i])) {
327: return false;
328: }
329: }
330: return true;
331: }
332:
333: /**
334: * Determines whether a type represented by a class object is
335: * convertible to another type represented by a class object using a
336: * method invocation conversion, treating object types of primitive
337: * types as if they were primitive types (that is, a Boolean actual
338: * parameter type matches boolean primitive formal type). This behavior
339: * is because this method is used to determine applicable methods for
340: * an actual parameter list, and primitive types are represented by
341: * their object duals in reflective method calls.
342: * @param formal the formal parameter type to which the actual
343: * parameter type should be convertible
344: * @param actual the actual parameter type.
345: * @return true if either formal type is assignable from actual type,
346: * or formal is a primitive type and actual is its corresponding object
347: * type or an object type of a primitive type that can be converted to
348: * the formal type.
349: */
350: private static boolean isMethodInvocationConvertible(
351: Class formal, Class actual) {
352: // Check for identity or widening reference conversion
353: if (formal.isAssignableFrom(actual)) {
354: return true;
355: }
356: // Check for boxing with widening primitive conversion. Note that
357: // actual parameters are never primitives.
358: if (formal.isPrimitive()) {
359: if (formal == Boolean.TYPE && actual == Boolean.class)
360: return true;
361: if (formal == Character.TYPE
362: && actual == Character.class)
363: return true;
364: if (formal == Byte.TYPE && actual == Byte.class)
365: return true;
366: if (formal == Short.TYPE
367: && (actual == Short.class || actual == Byte.class))
368: return true;
369: if (formal == Integer.TYPE
370: && (actual == Integer.class
371: || actual == Short.class || actual == Byte.class))
372: return true;
373: if (formal == Long.TYPE
374: && (actual == Long.class
375: || actual == Integer.class
376: || actual == Short.class || actual == Byte.class))
377: return true;
378: if (formal == Float.TYPE
379: && (actual == Float.class
380: || actual == Long.class
381: || actual == Integer.class
382: || actual == Short.class || actual == Byte.class))
383: return true;
384: if (formal == Double.TYPE
385: && (actual == Double.class
386: || actual == Float.class
387: || actual == Long.class
388: || actual == Integer.class
389: || actual == Short.class || actual == Byte.class))
390: return true;
391: }
392: // Special case for BigDecimals as we deem BigDecimal to be
393: // convertible to any numeric type - either object or primitive.
394: // This can actually cause us trouble as this is a narrowing
395: // conversion, not widening.
396: return isBigDecimalConvertible(formal, actual);
397: }
398:
399: private String listArgumentTypes() {
400: StringBuffer buf = new StringBuffer(classes.length * 32)
401: .append('(');
402: for (int i = 0; i < classes.length; ++i) {
403: buf.append(classes[i].getName()).append(',');
404: }
405: buf.setLength(buf.length() - 1);
406: return buf.append(')').toString();
407: }
408: }
409:
410: /**
411: * Determines whether a type represented by a class object is
412: * convertible to another type represented by a class object using a
413: * method invocation conversion, without matching object and primitive
414: * types. This method is used to determine the more specific type when
415: * comparing signatures of methods.
416: * @return true if either formal type is assignable from actual type,
417: * or formal and actual are both primitive types and actual can be
418: * subject to widening conversion to formal.
419: */
420: private static boolean isMoreSpecific(Class specific, Class generic) {
421: // Check for identity or widening reference conversion
422: if (generic.isAssignableFrom(specific)) {
423: return true;
424: }
425: // Check for widening primitive conversion.
426: if (generic.isPrimitive()) {
427: if (generic == Short.TYPE && (specific == Byte.TYPE))
428: return true;
429: if (generic == Integer.TYPE
430: && (specific == Short.TYPE || specific == Byte.TYPE))
431: return true;
432: if (generic == Long.TYPE
433: && (specific == Integer.TYPE
434: || specific == Short.TYPE || specific == Byte.TYPE))
435: return true;
436: if (generic == Float.TYPE
437: && (specific == Long.TYPE
438: || specific == Integer.TYPE
439: || specific == Short.TYPE || specific == Byte.TYPE))
440: return true;
441: if (generic == Double.TYPE
442: && (specific == Float.TYPE || specific == Long.TYPE
443: || specific == Integer.TYPE
444: || specific == Short.TYPE || specific == Byte.TYPE))
445: return true;
446: }
447: return isBigDecimalConvertible(generic, specific);
448: }
449:
450: private static boolean isBigDecimalConvertible(Class formal,
451: Class actual) {
452: // BigDecimal
453: if (BIGDECIMAL_CLASS.isAssignableFrom(actual)) {
454: if (NUMBER_CLASS.isAssignableFrom(formal)) {
455: return true;
456: }
457: if (formal.isPrimitive() && formal != Boolean.TYPE
458: && formal != Character.TYPE) {
459: return true;
460: }
461: }
462: return false;
463: }
464:
465: private static Class getMostSpecificCommonType(Class c1, Class c2) {
466: if (c1 == c2) {
467: return c1;
468: }
469: if (c2.isPrimitive()) {
470: if (c2 == Byte.TYPE)
471: c2 = Byte.class;
472: else if (c2 == Short.TYPE)
473: c2 = Short.class;
474: else if (c2 == Character.TYPE)
475: c2 = Character.class;
476: else if (c2 == Integer.TYPE)
477: c2 = Integer.class;
478: else if (c2 == Float.TYPE)
479: c2 = Float.class;
480: else if (c2 == Long.TYPE)
481: c2 = Long.class;
482: else if (c2 == Double.TYPE)
483: c2 = Double.class;
484: }
485: Set a1 = getAssignables(c1, c2);
486: Set a2 = getAssignables(c2, c1);
487: a1.retainAll(a2);
488: if (a1.isEmpty()) {
489: // Can happen when at least one of the arguments is an interface, as
490: // they don't have Object at the root of their hierarchy
491: return Object.class;
492: }
493: // Gather maximally specific elements. Yes, there can be more than one
494: // thank to interfaces. I.e., if you call this method for String.class
495: // and Number.class, you'll have Comparable, Serializable, and Object as
496: // maximal elements.
497: List max = new ArrayList();
498: outer: for (Iterator iter = a1.iterator(); iter.hasNext();) {
499: Class clazz = (Class) iter.next();
500: for (Iterator maxiter = max.iterator(); maxiter.hasNext();) {
501: Class maxClazz = (Class) maxiter.next();
502: if (isMoreSpecific(maxClazz, clazz)) {
503: // It can't be maximal, if there's already a more specific
504: // maximal than it.
505: continue outer;
506: }
507: if (isMoreSpecific(clazz, maxClazz)) {
508: // If it's more specific than a currently maximal element,
509: // that currently maximal is no longer a maximal.
510: maxiter.remove();
511: }
512: }
513: // If we get here, no current maximal is more specific than the
514: // current class, so it is considered maximal as well
515: max.add(clazz);
516: }
517: if (max.size() > 1) {
518: return OBJECT_CLASS;
519: }
520: return (Class) max.get(0);
521: }
522:
523: private static Set getAssignables(Class c1, Class c2) {
524: Set s = new HashSet();
525: collectAssignables(c1, c2, s);
526: return s;
527: }
528:
529: private static void collectAssignables(Class c1, Class c2, Set s) {
530: if (c1.isAssignableFrom(c2)) {
531: s.add(c1);
532: }
533: Class sc = c1.getSuperclass();
534: if (sc != null) {
535: collectAssignables(sc, c2, s);
536: }
537: Class[] itf = c1.getInterfaces();
538: for (int i = 0; i < itf.length; ++i) {
539: collectAssignables(itf[i], c2, s);
540: }
541: }
542: }
|