001: /*
002: * Created on Nov 26, 2004
003: */
004: package uk.org.ponder.reflect;
005:
006: import java.lang.reflect.Array;
007: import java.lang.reflect.Field;
008: import java.lang.reflect.Method;
009: import java.lang.reflect.Modifier;
010: import java.util.ArrayList;
011: import java.util.Collection;
012: import java.util.HashMap;
013: import java.util.HashSet;
014: import java.util.List;
015: import java.util.Map;
016: import java.util.Set;
017:
018: import uk.org.ponder.conversion.StaticLeafParser;
019: import uk.org.ponder.stringutil.StringList;
020: import uk.org.ponder.util.Logger;
021:
022: /**
023: * @author Antranig Basman (antranig@caret.cam.ac.uk)
024: *
025: */
026: public class ReflectUtils {
027:
028: public static boolean isPublicStatic(int modifiers) {
029: return Modifier.isPublic(modifiers)
030: && Modifier.isStatic(modifiers);
031: }
032:
033: /**
034: * Determine if the given type is assignable from the given value, assuming
035: * setting by reflection. Considers primitive wrapper classes as assignable to
036: * the corresponding primitive types. <p/>For example used in a bean factory's
037: * constructor resolution. <p/>Taken from Spring Framework "BeanUtils.java"
038: * release version 2.0.5
039: *
040: * @author Rod Johnson
041: * @author Juergen Hoeller
042: * @author Rob Harrop
043: * @param type the target type
044: * @param value the value that should be assigned to the type
045: * @return if the type is assignable from the value
046: */
047: public static boolean isAssignable(Class type, Object value) {
048: return (value != null ? isAssignable(type, value.getClass())
049: : !type.isPrimitive());
050: }
051:
052: /**
053: * Determine if the given target type is assignable from the given value type,
054: * assuming setting by reflection. Considers primitive wrapper classes as
055: * assignable to the corresponding primitive types.
056: *
057: * @param declaredType the target type
058: * @param objectType the value type that should be assigned to the target type
059: * @return if the target type is assignable from the value type
060: */
061: public static boolean isAssignable(Class declaredType,
062: Class objectType) {
063: return (declaredType.isAssignableFrom(objectType) || StaticLeafParser
064: .wrapClass(declaredType) == objectType);
065: }
066:
067: /**
068: * Determine a weight that represents the class hierarchy difference between
069: * types and arguments. A direct match, i.e. type Integer -> arg of class
070: * Integer, does not increase the result - all direct matches means weight 0.
071: * A match between type Object and arg of class Integer would increase the
072: * weight by 2, due to the superclass 2 steps up in the hierarchy (i.e.
073: * Object) being the last one that still matches the required type Object.
074: * Type Number and class Integer would increase the weight by 1 accordingly,
075: * due to the superclass 1 step up the hierarchy (i.e. Number) still matching
076: * the required type Number. Therefore, with an arg of type Integer, a
077: * constructor (Integer) would be preferred to a constructor (Number) which
078: * would in turn be preferred to a constructor (Object). All argument weights
079: * get accumulated.
080: * </p> This method will assign a penalty of 1024 to an invocation that would
081: * require a type conversion (currently only default leaf conversions from String
082: * are considered)
083: * </p>
084: * This method adapted from Spring framework AutowireUtils.java release version
085: * 2.0.5
086: *
087: * @author Juergen Hoeller
088: * @param argTypes the argument types to match
089: * @param args the arguments to match
090: * @return the accumulated weight for all arguments
091: */
092: public static int getTypeDifferenceWeight(Class[] argTypes,
093: Object[] args) {
094: int result = 0;
095: for (int i = 0; i < argTypes.length; i++) {
096: Object arg = args[i];
097: if (!isAssignable(argTypes[i], arg)) {
098: if (arg instanceof String
099: && StaticLeafParser.instance().isLeafType(
100: argTypes[i])) {
101: result += 1024;
102: } else
103: return Integer.MAX_VALUE;
104: }
105: if (args[i] != null) {
106: Class super Class = arg.getClass().getSuperclass();
107: while (super Class != null) {
108: if (isAssignable(argTypes[i], super Class)) {
109: result++;
110: super Class = super Class.getSuperclass();
111: } else {
112: super Class = null;
113: }
114: }
115: }
116: }
117: return result;
118: }
119:
120: public static Method[] getMatchingMethods(Class clazz,
121: String methodname, Object[] args) {
122: Method[] allMethods = clazz.getMethods();
123: int bestMatch = Integer.MAX_VALUE;
124: List togo = new ArrayList();
125: for (int i = 0; i < allMethods.length; ++i) {
126: Method method = allMethods[i];
127: if (!method.getName().equals(methodname)
128: || method.getParameterTypes().length != args.length)
129: continue;
130: int diff = getTypeDifferenceWeight(method
131: .getParameterTypes(), args);
132: if (diff < bestMatch) {
133: togo.clear();
134: bestMatch = diff;
135: }
136: if (diff <= bestMatch) {
137: togo.add(method);
138: }
139: }
140: return (Method[]) togo.toArray(new Method[togo.size()]);
141: }
142:
143: /** Determines whether an object is capable of supporting a method invocation
144: * with a given name. Used primarily to resolve potential ambiguity in EL
145: * expressions that are being interpreted as method bindings.
146: */
147: public static boolean hasMethod(Object obj, String methodname) {
148: if (obj instanceof MethodInvokingProxy)
149: return true;
150: Method[] allMethods = obj.getClass().getMethods();
151: for (int i = 0; i < allMethods.length; ++i) {
152: Method method = allMethods[i];
153: if (method.getName().equals(methodname))
154: return true;
155: }
156: return false;
157: }
158:
159: /**
160: * Returns a list of all superclasses and implemented interfaces by the
161: * supplied class, recursively to the base, up to but excluding Object.class.
162: * These will be listed in order from the supplied class, all concrete
163: * superclasses in ascending order, and then finally all interfaces in
164: * recursive ascending order.
165: */
166:
167: public static List getSuperclasses(Class clazz) {
168: List togo = new ArrayList();
169: while (clazz != Object.class) {
170: togo.add(clazz);
171: clazz = clazz.getSuperclass();
172: }
173: int super s = togo.size();
174: for (int i = 0; i < super s; ++i) {
175: appendSuperclasses((Class) togo.get(i), togo);
176: }
177: return togo;
178: }
179:
180: private static void appendSuperclasses(Class clazz, List accrete) {
181: Class[] interfaces = clazz.getInterfaces();
182: for (int i = 0; i < interfaces.length; ++i) {
183: accrete.add(interfaces[i]);
184: }
185: for (int i = 0; i < interfaces.length; ++i) {
186: appendSuperclasses(interfaces[i], accrete);
187: }
188: }
189:
190: public static final int UNKNOWN_SIZE = -1;
191:
192: /**
193: * Instantiates a "default" type of container conforming to a given interface
194: * and of a given size. If asked to create an array of unknown size, will
195: * return an zero-element array instead, signalling a "placeholder".
196: */
197: public static Object instantiateContainer(Class declaredtype,
198: int size, ReflectiveCache cache) {
199: // only missing case is if someone madly declares an argument of type
200: // "AbstractList" or similar...
201: if (declaredtype == List.class
202: || declaredtype == Collection.class) {
203: return size == UNKNOWN_SIZE ? new ArrayList()
204: : new ArrayList(size);
205: } else if (declaredtype == Set.class) {
206: return size == UNKNOWN_SIZE ? new HashSet() : new HashSet(
207: size);
208: } else if (declaredtype == Map.class) {
209: return size == UNKNOWN_SIZE ? new HashMap() : new HashMap(
210: size);
211: } else if (declaredtype.isArray()) {
212: if (size == UNKNOWN_SIZE)
213: size = 0;
214: // erm, this is a native method! How long does it take exactly!
215: Class component = declaredtype.getComponentType();
216: if (component == Object.class) {
217: // the most common case.
218: return new Object[size];
219: } else {
220: return Array.newInstance(component, size);
221: }
222: }
223: // else it had better be something default-constructible.
224: else
225: return cache.construct(declaredtype);
226: }
227:
228: public static final boolean PREFIX = true;
229: public static final boolean SUFFIX = false;
230:
231: public static void addStaticStrings(Object provider, String xfix,
232: boolean prefix, StringList strings) {
233: Class provclass = provider instanceof Class ? (Class) provider
234: : provider.getClass();
235: Field[] fields = provclass.getFields();
236: for (int j = 0; j < fields.length; ++j) {
237: Field field = fields[j];
238: try {
239: int modifiers = field.getModifiers();
240: if (!isPublicStatic(modifiers))
241: continue;
242: String fieldname = field.getName();
243: if ((prefix ? fieldname.startsWith(xfix) : fieldname
244: .endsWith(xfix))
245: && field.getType() == String.class) {
246: strings.add(field.get(provider));
247: }
248: } catch (Throwable t) {
249: Logger.log.fatal("Error reflecting for static names ",
250: t);
251: }
252: }
253: }
254: }
|