Source code: javax/ide/extension/DynamicHook.java
1 package javax.ide.extension;
2
3 import java.lang.reflect.InvocationTargetException;
4 import java.lang.reflect.Method;
5
6 import java.util.ArrayList;
7 import java.util.List;
8 import java.util.ListIterator;
9 import java.util.logging.Level;
10
11 import javax.ide.extension.spi.Stack;
12
13 /**
14 * An implementation of <tt>ExtensionHook</tt> that automatically populates
15 * model objects using reflection.
16 */
17 public class DynamicHook extends ExtensionHook
18 {
19 public final String sApplicationObjectKey =
20 DynamicHook.class.getName() + ".appObjectKey";
21
22 private static final String ATTRIBUTE_CLASS = "class";
23 private static final String SET_METHOD_PREFIX = "set";
24 private static final String ADD_METHOD_PREFIX = "add";
25 private static final Class[] sObjectParamTypes = { Object.class };
26 private static final Class[] sStringParamTypes = { String.class };
27
28 /**
29 * This stack references the application objects currently in scope. After
30 * creation, an application object is pushed onto the stack. When that
31 * complex element goes out of scope, the object is popped from the stack.
32 */
33 private final Stack _applicationObjectStack = new Stack();
34
35 /**
36 * This stack references a verdict on whether the element in scope is complex
37 * or simple. When the element goes out of scope, the verdict on the parent
38 * element is available at the top of this stack.
39 */
40 private final Stack _complexTypeIndicatorStack = new Stack();
41
42 /**
43 * If non-null, this classloader is passed to all calls to Class.newInstance()
44 * , otherwise the current thread context classloader is used.
45 */
46 private ClassLoader _classLoader;
47
48 /**
49 * A list of registered {@link ElementTypeResolver}s which may be queried for
50 * the runtime type corresponding to an xml element.
51 */
52 private final List _resolvers = new ArrayList(5);
53
54 public DynamicHook(Object rootObject)
55 {
56 // Push the root object into first place on the stack.
57 _applicationObjectStack.push(rootObject);
58 }
59
60 public DynamicHook(Object rootObject, ClassLoader classLoader)
61 {
62 this(rootObject);
63 _classLoader = classLoader;
64 }
65
66 public DynamicHook(
67 Object rootObject,
68 ClassLoader classLoader,
69 ElementTypeResolver resolver)
70 {
71 this(rootObject, classLoader);
72 _resolvers.add(resolver);
73 }
74
75 public void registerElementTypeResolver(ElementTypeResolver resolver)
76 {
77 _resolvers.add(resolver);
78 }
79
80 public void start(ElementStartContext context)
81 {
82 Class runtimeType = getRuntimeType(context);
83 _complexTypeIndicatorStack.push(new boolean[]
84 {
85 runtimeType != null ? true : false
86 });
87 if (runtimeType != null)
88 {
89 // Retrieve the application object for the complex element.
90 Object o = getApplicationObject(runtimeType, context);
91
92 // Pre initialization hook.
93 invokePreInitialize(_applicationObjectStack.peek(), o);
94
95 // Push the application object onto the stack so that it becomes the
96 // object in scope for child elements.
97 _applicationObjectStack.push(o);
98
99 // Push the application object onto the context map in scope. This makes
100 // it available globally under the sApplicationObjectKey key. Note:
101 // keeping a separate stack of app objects maintains the integrity of the
102 // dynamic handler instance, since child handlers are at liberty to remove
103 // an object from the context map.
104 context.getScopeData().put(sApplicationObjectKey, o);
105
106 // Additional complex element start behaviour, e.g. specialized attribute
107 // handling.
108 handleComplexElementStart(o, context);
109 }
110 else
111 {
112 // Additional simple element start behaviour, e.g. specialized attribute
113 // handling.
114 handleSimpleElementStart(context);
115 }
116 }
117
118 public void end(ElementEndContext context)
119 {
120 boolean isComplex = ((boolean[])_complexTypeIndicatorStack.pop())[0];
121 if (isComplex)
122 {
123 // Reference the child object.
124 Object child = _applicationObjectStack.pop();
125
126 // Post initialization hook.
127 invokePostInitialize(child);
128
129 // Attach the child and parent objects.
130 attachObject(_applicationObjectStack.peek(), child, context);
131
132 // Hook for additional complex element end behaviour (none by default).
133 handleComplexElementEnd(context);
134 }
135 else
136 {
137 // Attach any simple element data to the application object in scope.
138 attachData(_applicationObjectStack.peek(), context.getText(), context);
139
140 // Hook for additional simple element end behaviour (none by default).
141 handleSimpleElementEnd(context);
142 }
143 }
144
145 protected void handleComplexElementStart(
146 Object applicationObject,
147 ElementStartContext context)
148 {
149 // This basic implementation only knows about the 'class' attribute for
150 // complex elements. However, specializations may override at this point
151 // to plug in additional behaviour.
152 }
153
154 protected void handleComplexElementEnd(ElementEndContext context)
155 {
156 // This implementation takes no more action on ending of a complex element
157 // after the child object has been attached to the parent object.
158 }
159
160 protected void handleSimpleElementStart(ElementStartContext context)
161 {
162 // This basic implementation ignores all attributes for simple elements.
163 // However, specializations may override at this point to plug in additional
164 // behaviour.
165 }
166
167 protected void handleSimpleElementEnd(ElementEndContext context)
168 {
169 // This implementation takes no more action on ending of a simple element
170 // after the child data has been attached to the object in scope.
171 }
172
173 protected Class getRuntimeType(ElementStartContext context)
174 {
175 // First, interrogate the 'class' attribute. Failing that, interrogate the
176 // registered resolvers in reverse order.
177 Class type = null;
178 String classAttribute = context.getAttributeValue(ATTRIBUTE_CLASS);
179 if (classAttribute != null)
180 {
181 try
182 {
183 type = Class.forName(classAttribute, true, _classLoader != null ?
184 _classLoader : Thread.currentThread().getContextClassLoader());
185 }
186 catch (ClassNotFoundException cnfe)
187 {
188 log( context, Level.SEVERE, "Unable to load class: " + classAttribute );
189 }
190 }
191 else
192 {
193 ListIterator it = _resolvers.listIterator(_resolvers.size());
194 while (it.hasPrevious() && type == null)
195 {
196 type = ((ElementTypeResolver)it.previous()).resolveType(
197 context.getElementName() );
198 }
199 }
200 return type;
201 }
202
203 protected Object getApplicationObject(
204 Class runtimeType,
205 ElementStartContext context)
206 {
207 // In this simple implementation, the only information needed to retrieve
208 // the application object is its type, however extra context is provided
209 // in this method for the benefit of specializations.
210 try
211 {
212 return runtimeType.newInstance();
213 }
214 catch (Exception e)
215 {
216 log( context, Level.SEVERE, "Unable to instantiate class: " +
217 runtimeType.getName() );
218 e.printStackTrace();
219 return null;
220 }
221 }
222
223 protected void attachObject(
224 Object parent,
225 Object child,
226 ElementEndContext context)
227 {
228 // Find the relevant setXXX(Object) or addXXX(Object) method on the
229 // parent application object.
230 Method m = findMethod(context, parent, context.getElementName().getLocalName(),
231 sObjectParamTypes);
232
233 // Dynamically invoke the target method, passing the child object as the
234 // parameter to the target method.
235 try
236 {
237 m.invoke(parent, new Object[] {child});
238 }
239 catch (InvocationTargetException ite)
240 {
241 StringBuffer b = new StringBuffer(200);
242 b.append("Could not attach child object: ").append(child.toString());
243 b.append(" to parent: ").append(parent.toString());
244 b.append(". Root cause: ");
245 b.append(ite.getTargetException().getClass().getName()).append(": ");
246 b.append(ite.getTargetException().getMessage());
247 log( context, Level.SEVERE, b.toString() );
248 }
249 catch (Exception e)
250 {
251 StringBuffer b = new StringBuffer(200);
252 b.append("Could not attach child object: ").append(child.toString());
253 b.append(" to parent: ").append(parent.toString());
254 b.append(". Root cause: ");
255 b.append(e.getClass().getName()).append(": ").append(e.getMessage());
256 log( context, Level.SEVERE, b.toString() );
257 }
258 }
259
260 protected void attachData(
261 Object parent,
262 String data,
263 ElementEndContext context)
264 {
265 // Find the appropriate setXXX(String) method.
266 Method m = findMethod(context, parent, context.getElementName().getLocalName(),
267 sStringParamTypes);
268
269 // Invoke the method.
270 try
271 {
272 m.invoke(parent, new Object[] { data });
273 }
274 catch (InvocationTargetException ite)
275 {
276 log( context, Level.SEVERE,
277 "Unable to attach data '" + data + "' for simple element " +
278 context.getElementName().getLocalName() );
279 ite.getTargetException().printStackTrace();
280 }
281 catch (Exception e)
282 {
283 log( context, Level.SEVERE, "Unable to attach data '" + data +
284 "' for simple element " + context.getElementName().getLocalName());
285 e.printStackTrace();
286 }
287 }
288
289 protected void invokePreInitialize(Object child, Object parent)
290 {
291 // TODO: Define a preinit hook?
292 // E.g: ((InitializableXMLObject)child).preInit(parent);
293 }
294
295 protected void invokePostInitialize(Object o)
296 {
297 // TODO: Define a postInit hook.
298 // E.g: ((InitializableXMLObject)o).postInit();
299 }
300
301 protected Method findMethod(ElementContext context,
302 Object o, String elementName, Class[] paramTypes)
303 {
304 String methodName;
305 try
306 {
307 methodName = getMethodName(SET_METHOD_PREFIX, elementName);
308 return o.getClass().getMethod(methodName, paramTypes);
309 }
310 catch (NoSuchMethodException nsme)
311 {
312 try
313 {
314 methodName = getMethodName(ADD_METHOD_PREFIX, elementName);
315 return o.getClass().getMethod(methodName, paramTypes);
316 }
317 catch (NoSuchMethodException nme)
318 {
319 log( context, Level.SEVERE, "Class " + o.getClass().getName() +
320 " has no set or add method for element named " + elementName );
321 nme.printStackTrace();
322 }
323 }
324 return null;
325 }
326
327 protected String getMethodName(String prefix, String elementName)
328 {
329 char[] name = new char[prefix.length() + elementName.length()];
330 for (int i = 0; i < prefix.length(); i++)
331 {
332 name[i] = prefix.charAt(i);
333 }
334 boolean nextUpper = true;
335 int nameIndex = prefix.length();
336 for (int i = 0; i < elementName.length(); i++)
337 {
338 if (Character.isJavaIdentifierPart(elementName.charAt(i)))
339 {
340 if (nextUpper)
341 {
342 name[nameIndex] =
343 Character.toUpperCase(elementName.charAt(i));
344 nextUpper = false;
345 }
346 else
347 {
348 name[nameIndex] = elementName.charAt(i);
349 }
350 nameIndex++;
351 }
352 else
353 {
354 nextUpper = true;
355 }
356 }
357 return new String(name).trim();
358 }
359
360 /**
361 * An object which can resolve an ElementName into a Class type for that
362 * element.
363 */
364 public static interface ElementTypeResolver
365 {
366 /**
367 * Resolves a fully-qualified element name to a runtime type which is capable
368 * of consuming information in child elements as laid out by the rules
369 * defined by {@link DynamicHook}.
370 *
371 * @param elementName the fully qualified name of the element to be
372 * resolved.
373 * @return a {@link java.lang.Class} representing the runtime type capable
374 * of consuming the xml, or <code>null</code> if no matching type is found.
375 */
376 public Class resolveType( ElementName elementName );
377 }
378
379 }