001: package org.josql.internal;
002:
003: import java.util.List;
004: import java.util.ArrayList;
005: import java.util.TreeMap;
006:
007: import java.lang.reflect.Method;
008: import java.lang.reflect.Modifier;
009: import java.lang.reflect.Field;
010: import java.lang.reflect.InvocationTargetException;
011:
012: import java.util.StringTokenizer;
013:
014: import com.gentlyweb.utils.Getter;
015:
016: /**
017: * This class is used to perform access into a Java object using a
018: * String value with a specific notation.
019: * <p>
020: * The Accessor uses a dot notation such as <b>field1.method1.method2</b> to
021: * perform the access on an object. Each value in the notation refers to
022: * a field or method (a no argument method) of the type of the previous
023: * value.
024: * For instance if you have the following class structure:
025: * </p>
026: * <pre>
027: * public class A
028: * {
029: * public B = new B ();
030: * }
031: *
032: * public class B
033: * {
034: * public C = new C ();
035: * }
036: *
037: * public class C
038: * {
039: * String d = "";
040: * }
041: * </pre>
042: * <p>
043: * You would then use the notation: <b>B.C.d</b> to get access to
044: * field <b>d</b> in Class C.
045: * <br /><br />
046: * The Accessor also supports a <b>[ ]</b> notation for accessing
047: * into Lists/Maps and Arrays. If the value between the <b>[ ]</b>
048: * is an integer then we look for the associated type to be either
049: * an array or a List, we then index into it with the integer. If
050: * the value is <b>NOT</b> an integer then we use assume the
051: * type is a Map and use it as a key into the Map.
052: * <br /><br />
053: * For instance changing the example above:
054: * </p>
055: * <pre>
056: * public class A
057: * {
058: * public List vals = new ArrayList ();
059: * }
060: * </pre>
061: * <p> Method[] methods = c.getMethods ();
062:
063: for (int i = 0; i < methods.length; i++)
064: {
065:
066: if ((methods[i].getName ().equals (getMethod.toString ()))
067: &&
068: (methods[i].getParameterTypes ().length == 0)
069: )
070: {
071:
072: // This is the one...
073: return methods[i];
074:
075: }
076:
077: }
078:
079: return null;
080:
081: * Now we could use: <b>vals[X]</b> where <b>X</b> is a positive integer.
082: * Or changing again:
083: * </p>
084: * <pre>
085: * public class A
086: * {
087: * public Map vals = new HashMap ();
088: * }
089: * </pre>
090: * <p>
091: * We could use: <b>vals[VALUE]</b> where <b>VALUE</b> would then be
092: * used as a Key into the vals HashMap.
093: * <br /><br />
094: * Note: The Accessor is <b>NOT</b> designed to be an all purpose
095: * method of gaining access to a class. It has specific uses and for
096: * most will be of no use at all. It should be used for general purpose
097: * applications where you want to access specific fields of an object
098: * without having to know the exact type. One such application is in
099: * the <code>GeneralComparator</code>, in that case arbitrary Objects can
100: * be sorted without having to write complex Comparators or implementing
101: * the Comparable interface AND it gives the flexibility that sorting
102: * can be changed ad-hoc.
103: * <br /><br />
104: * The Accessor looks for in the following order:
105: * <ul>
106: * <li>Public fields with the specified name.</li>
107: * <li>If no field is found then the name is converted to a "JavaBeans"
108: * <b>get</b> method, so a field name of <b>value</b> would be converted
109: * to <b>getValue</b> and that method is looked for. The method must take
110: * no arguments.</li>
111: * <li>If we don't find the <b>get*</b> method then we look for a method with
112: * the specified name. So a field name of <b>value</b> would mean that
113: * a method (that again takes no arguments) is looked for.</li>
114: * </ul>
115: * <p>
116: * Note: we have had to add the 3rd type to allow for methods that don't follow
117: * JavaBeans conventions (there are loads in the standard Java APIs which makes
118: * accessing impossible otherwise).
119: */
120: public class Setter {
121:
122: private Getter getter = null;
123: private Object setter = null;
124: private Class clazz = null;
125:
126: /**
127: * @param ref The reference for the setter.
128: * @param clazz The Class to get the field from.
129: */
130: public Setter(String ref, Class clazz, Class[] parmTypes)
131: throws IllegalArgumentException, NoSuchMethodException {
132:
133: this .clazz = clazz;
134:
135: StringTokenizer t = new StringTokenizer(ref, ".");
136:
137: StringBuffer getRef = new StringBuffer();
138:
139: if (t.countTokens() > 1) {
140:
141: // Get everything up to the last part.
142: while (t.hasMoreTokens()) {
143:
144: getRef.append(t.nextToken());
145:
146: if (t.countTokens() > 1) {
147:
148: getRef.append('.');
149:
150: }
151:
152: if (t.countTokens() == 1) {
153:
154: // Now get the Getter.
155: this .getter = new Getter(getRef.toString(), clazz);
156:
157: // Get the return type from the getter.
158: clazz = this .getter.getType();
159:
160: break;
161:
162: }
163:
164: }
165:
166: }
167:
168: // Now for the final part this is the setter.
169: String set = t.nextToken();
170:
171: // Get the Fields.
172: Field[] fields = clazz.getFields();
173:
174: Field f = null;
175:
176: // See if the token matches...
177: for (int i = 0; i < fields.length; i++) {
178:
179: if (fields[i].getName().equals(set)) {
180:
181: // Found it...
182: f = fields[i];
183:
184: this .setter = f;
185:
186: return;
187:
188: }
189:
190: }
191:
192: // If we are here then it's not a public field.
193:
194: // Now convert it to a method name and use the
195: // JavaBeans convention...
196:
197: // Now get the method...
198: StringBuffer name = new StringBuffer(set);
199:
200: name.setCharAt(0, Character.toUpperCase(name.charAt(0)));
201:
202: name.insert(0, "set");
203:
204: String nName = name.toString();
205:
206: List meths = new ArrayList();
207:
208: Utilities.getMethods(clazz, nName, Modifier.PUBLIC, meths);
209:
210: TreeMap sm = new TreeMap();
211:
212: // Now compare the parm types.
213: for (int i = 0; i < meths.size(); i++) {
214:
215: Method m = (Method) meths.get(i);
216:
217: Class[] mpts = m.getParameterTypes();
218:
219: int score = Utilities.matchMethodArgs(mpts, parmTypes);
220:
221: if (score > 0) {
222:
223: sm.put(Integer.valueOf(score), m);
224:
225: }
226:
227: }
228:
229: // Get the last key
230: if (sm.size() > 0) {
231:
232: this .setter = (Method) sm.get(sm.lastKey());
233:
234: }
235:
236: if (this .setter == null) {
237:
238: meths = new ArrayList();
239:
240: Utilities.getMethods(clazz, set, Modifier.PUBLIC, meths);
241:
242: sm = new TreeMap();
243:
244: // Now compare the parm types.
245: for (int i = 0; i < meths.size(); i++) {
246:
247: Method m = (Method) meths.get(i);
248:
249: Class[] mpts = m.getParameterTypes();
250:
251: int score = Utilities.matchMethodArgs(mpts, parmTypes);
252:
253: if (score > 0) {
254:
255: sm.put(Integer.valueOf(score), m);
256:
257: }
258:
259: }
260:
261: // Get the last key
262: if (sm.size() > 0) {
263:
264: this .setter = (Method) sm.get(sm.lastKey());
265:
266: }
267:
268: }
269:
270: if (this .setter == null) {
271:
272: throw new IllegalArgumentException(
273: "Unable to find required method: " + nName
274: + " or: " + set + " in class: "
275: + clazz.getName() + " taking parms: "
276: + parmTypes);
277:
278: }
279:
280: }
281:
282: public Class getBaseClass() {
283:
284: return this .clazz;
285:
286: }
287:
288: public void setValue(Object target, Object value)
289: throws IllegalAccessException, InvocationTargetException,
290: IllegalArgumentException {
291:
292: Object[] vals = { value };
293:
294: this .setValue(target, vals);
295:
296: }
297:
298: public void setValue(Object target, Object[] values)
299: throws IllegalAccessException, InvocationTargetException,
300: IllegalArgumentException {
301:
302: // Get the object to set on from the getter.
303: if (this .getter != null) {
304:
305: target = this .getter.getValue(target);
306:
307: }
308:
309: // Now call our accessor on the obj and set the value.
310: if (this .setter instanceof Field) {
311:
312: Field f = (Field) this .setter;
313:
314: f.set(target, values[0]);
315:
316: return;
317:
318: }
319:
320: if (this .setter instanceof Method) {
321:
322: Method m = (Method) this .setter;
323:
324: m.invoke(target, Utilities.convertArgs(values, m
325: .getParameterTypes()));
326:
327: }
328:
329: }
330:
331: /**
332: * Get the class of the type of object we expect in the {@link #setValue(Object,Object)}
333: * method.
334: *
335: * @return The class.
336: */
337: public Class getType() {
338:
339: // See what type the accessor is...
340: if (this .setter instanceof Method) {
341:
342: Method m = (Method) this .setter;
343:
344: Class[] parms = m.getParameterTypes();
345:
346: return parms[0];
347:
348: }
349:
350: if (this .setter instanceof Field) {
351:
352: // It's a field...so...
353: Field f = (Field) this.setter;
354:
355: return f.getType();
356:
357: }
358:
359: return null;
360:
361: }
362:
363: }
|