001: /**
002: * InstantJ
003: *
004: * Copyright (C) 2002 Nils Meier
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation; either
009: * version 2.1 of the License, or (at your option) any later version.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: *
016: */package instantj.reflect;
017:
018: import java.lang.reflect.Constructor;
019: import java.lang.reflect.Field;
020: import java.lang.reflect.Method;
021: import java.lang.reflect.InvocationTargetException;
022:
023: /**
024: * The class providing reflection operations - have a look at the
025: * list of methods to find out what this thing provides.
026: *
027: * @author <A href="mailto:nils@meiers.net">Nils Meier</A>
028: */
029: public class ReflectAccess {
030:
031: /** the singleton instance */
032: private static ReflectAccess singleton = new ReflectAccess();
033:
034: /** the usual getter */
035: private static String GET = "get";
036:
037: /** the 'is' getter */
038: private static String IS = "is";
039:
040: /** the usual setter */
041: private static String SET = "set";
042:
043: /** an empty class array */
044: private final static Class[] EMPTY_CLASS_ARRAY = new Class[0];
045:
046: /** a one-Integer class array */
047: private final static Class[] ONE_INT_CLASS_ARRAY = new Class[] { Integer.TYPE };
048:
049: /** an empty object array */
050: private final static Object[] EMPTY_OBJECT_ARRAY = new Object[0];
051:
052: /** a one-String class array */
053: private final static Class[] ONE_OBJECT_CLASS_ARRAY = new Class[] { Object.class };
054:
055: /**
056: * Returns a getter/setter method name for given property
057: */
058: public String calcAccessor(String prefix, String property) {
059: return prefix + property.substring(0, 1).toUpperCase()
060: + property.substring(1);
061: }
062:
063: /**
064: * Constructor
065: */
066: private ReflectAccess() {
067: }
068:
069: /**
070: * Singleton access
071: */
072: public static ReflectAccess getInstance() {
073: return singleton;
074: }
075:
076: /**
077: * Helper which calculates a Class array for given Object array
078: */
079: public Class[] calcClassesFromObjects(Object[] args) {
080:
081: // The empty case
082: if (args.length == 0) {
083: return EMPTY_CLASS_ARRAY;
084: }
085:
086: // Looping required
087: Class[] result = new Class[args.length];
088: for (int i = 0; i < result.length; i++) {
089: if (args[i] != null) {
090: result[i] = args[i].getClass();
091: } else {
092: result[i] = Object.class;
093: }
094: }
095:
096: // Done
097: return result;
098: }
099:
100: /**
101: * Returns the class name without package for given class
102: */
103: public String calcClassNameOf(Class theClass) {
104: String name = theClass.getName();
105: int pkg = name.lastIndexOf('.');
106: if (pkg < 0) {
107: return name;
108: }
109: return name.substring(pkg + 1);
110: }
111:
112: /**
113: * Returns a Method for the given object
114: */
115: public Method calcMethodFor(Object object, String method,
116: Class[] args) throws NoSuchMethodException {
117: // Hmmm, should this be getDeclaredMethod or getMethod?
118: return object.getClass().getMethod(method, args);
119: }
120:
121: /**
122: * Returns the package name without classname for given class
123: */
124: public String calcPkgNameOf(Class theClass) {
125:
126: String name = theClass.getName();
127: int pkg = name.lastIndexOf('.');
128: if (pkg < 0)
129: return "";
130:
131: return name.substring(0, pkg);
132: }
133:
134: /**
135: * Invokes a getter on given object - the property XYZ
136: * has to reflect the object's methods getXYZ or isXYZ
137: *
138: * Mental Note: if an inner type is 'private static' AND
139: * has a public method, an attempt to invoke it will lead
140: * to an IllegalAccessException (Arrays.asList() for example
141: * returns a type 'Arrays$ArrayList' that is 'private static')
142: * in JDK 1.3
143: */
144: public Object invokeGetter(Object object, String property)
145: throws IllegalPropertyException,
146: InaccessiblePropertyException {
147:
148: IllegalPropertyException retry = null;
149: while (true) {
150:
151: try {
152:
153: // The method to call
154: String accessor = (retry == null ? calcAccessor(GET,
155: property) : calcAccessor(IS, property));
156:
157: // Do the call
158: return invoke(object, accessor, EMPTY_OBJECT_ARRAY);
159:
160: } catch (NoSuchMethodException e) {
161: if (retry != null)
162: throw retry;
163: retry = new IllegalPropertyException(
164: "getter doesn't exist", object, property);
165: } catch (IllegalAccessException e) {
166: throw new InaccessiblePropertyException(
167: "getter couldn't be accessed", object, property);
168: } catch (InvocationTargetException e) {
169: throw new InaccessiblePropertyException(
170: "getter failed", object, property, e
171: .getTargetException());
172: }
173:
174: }
175:
176: }
177:
178: /**
179: * Invokes a setter on given object
180: */
181: public void invokeSetter(Object object, String property, Object arg)
182: throws IllegalPropertyException,
183: InaccessiblePropertyException {
184:
185: try {
186:
187: // The method to call
188: String accessor = calcAccessor(SET, property);
189:
190: // Do the call
191: invoke(object, accessor, calcArrayWrap(arg));
192:
193: } catch (NoSuchMethodException e) {
194: throw new IllegalPropertyException("setter doesn't exist",
195: object, property);
196: } catch (IllegalAccessException e) {
197: throw new InaccessiblePropertyException(
198: "setter couldn't be accessed", object, property);
199: } catch (InvocationTargetException e) {
200: throw new InaccessiblePropertyException("setter failed",
201: object, property, e.getTargetException());
202: }
203:
204: }
205:
206: /**
207: * Helper that wraps an Object into an Array
208: */
209: public Object[] calcArrayWrap(Object object) {
210: return new Object[] { object };
211: }
212:
213: /**
214: * Sets a field on given object
215: * @param object the instance to set property on
216: * @param property the property to set on the instance
217: * @param value the value to set
218: */
219: public void setField(Object object, String property, Object value)
220: throws IllegalPropertyException,
221: InaccessiblePropertyException {
222: setField(object, property, value, false);
223: }
224:
225: /**
226: * Sets a field on given object
227: * @param object the instance to set property on
228: * @param property the property to set on the instance
229: * @param value the value to set
230: * @param wrapValueIfNeeded whether to try to wrap a value with a field-type one-argument-constructor if necessary
231: */
232: public void setField(Object object, String property, Object value,
233: boolean wrapValueIfNeeded) throws IllegalPropertyException,
234: InaccessiblePropertyException {
235:
236: String fieldType = "<UNKNOWN>";
237: try {
238:
239: // Here's the target's type
240: Class type = object.getClass();
241:
242: // .. and the field we're going for
243: Field f = type.getDeclaredField(property);
244: fieldType = f.getType().getName();
245:
246: // .. last change to adjust the type
247: if (wrapValueIfNeeded
248: && (!f.getType().isAssignableFrom(value.getClass()))) {
249: value = calcWrapping(value, f.getType());
250: }
251:
252: // .. and the actual set
253: f.set(object, value);
254:
255: // Done
256: } catch (NoSuchFieldException e) {
257: throw new IllegalPropertyException("field doesn't exist",
258: object, property);
259: } catch (IllegalAccessException e) {
260: throw new InaccessiblePropertyException(
261: "field couldn't be accessed", object, property);
262: } catch (IllegalArgumentException e) {
263: throw new IllegalPropertyException(
264: "field type "
265: + fieldType
266: + " doesn't match value type "
267: + value.getClass().getName()
268: + " and wrapping with single-argument constructor was unsuccessfull",
269: object, property);
270: }
271:
272: }
273:
274: /**
275: * Invokes a given method on given object
276: */
277: public Object invoke(Object instance, String method, Object[] args)
278: throws NoSuchMethodException, IllegalAccessException,
279: InvocationTargetException {
280:
281: // Prepare the argument type array
282: Class[] argTypes = new Class[args.length];
283: for (int i = 0; i < argTypes.length; i++) {
284: argTypes[i] = args[i].getClass();
285: }
286:
287: // Calc the method
288: Method m = calcMethodFor(instance, method, argTypes);
289:
290: // Do the call
291: Object result = m.invoke(instance, args);
292:
293: // Done
294: return result;
295:
296: }
297:
298: /**
299: * Calculates a type for given name
300: *
301: * @param name the type's name
302: */
303: public Class calcClassFromName(String name)
304: throws IllegalArgumentException {
305:
306: // Check the usual suspects
307: if (Boolean.TYPE.getName().equals(name))
308: return Boolean.TYPE;
309: if (Integer.TYPE.getName().equals(name))
310: return Integer.TYPE;
311: if (Short.TYPE.getName().equals(name))
312: return Short.TYPE;
313: if (Character.TYPE.getName().equals(name))
314: return Character.TYPE;
315: if ("String".equals(name))
316: return String.class;
317: if ("Boolean".equals(name))
318: return Boolean.class;
319: if ("Integer".equals(name))
320: return Integer.class;
321: if ("Date".equals(name))
322: return java.util.Date.class;
323:
324: // otherwise load it
325: try {
326: return Class.forName(name.toString());
327: } catch (Throwable t) {
328: throw new IllegalArgumentException(
329: "Couldn't resolve type for name " + name);
330: }
331:
332: // Done
333: }
334:
335: /**
336: * Tries to wrap a given object in an instance of given target
337: * type by calling that type's one-argument-constructor.
338: * @param object the to be wrapped
339: * @param target the wrapper type
340: */
341: public Object calcWrapping(Object object, Class target)
342: throws IllegalArgumentException {
343:
344: // Check for primitive wrappers
345: if (Boolean.TYPE.equals(target)) {
346: target = Boolean.class;
347: }
348: if (Integer.TYPE.equals(target)) {
349: target = Integer.class;
350: }
351: if (Short.TYPE.equals(target)) {
352: target = Short.class;
353: }
354: if (Character.TYPE.equals(target)) {
355: target = Character.class;
356: }
357: if (Long.TYPE.equals(target)) {
358: target = Long.class;
359: }
360: if (Double.TYPE.equals(target)) {
361: target = Double.class;
362: }
363: if (Float.TYPE.equals(target)) {
364: target = Float.class;
365: }
366:
367: // Maybe it's assignable already
368: if (target.isAssignableFrom(object.getClass()))
369: return object;
370:
371: // Check the one-argument-constructor
372: try {
373: Constructor constructor = target
374: .getConstructor(new Class[] { object.getClass() });
375: return constructor.newInstance(new Object[] { object });
376: } catch (Throwable t) {
377: throw new IllegalArgumentException("Couldn't wrap "
378: + object.getClass() + " in an instance of "
379: + target);
380: }
381:
382: // Done
383: }
384:
385: }
|