Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

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 }