View Javadoc

1   /**
2    * 2007, Digitalis Informatica. All rights reserved. Distribuicao e Gestao de Informatica, Lda. Estrada de Paco de Arcos
3    * num.9 - Piso -1 2780-666 Paco de Arcos Telefone: (351) 21 4408990 Fax: (351) 21 4408999 http://www.digitalis.pt
4    */
5   
6   package pt.digitalis.dif.codegen.util;
7   
8   import java.util.HashMap;
9   import java.util.Map;
10  
11  import javassist.CannotCompileException;
12  import pt.digitalis.dif.codegen.CGAncillaries;
13  import pt.digitalis.dif.codegen.templates.ApplicationCGTemplate;
14  import pt.digitalis.dif.codegen.templates.ProviderCGTemplate;
15  import pt.digitalis.dif.codegen.templates.ServiceCGTemplate;
16  import pt.digitalis.dif.codegen.templates.StageCGTemplate;
17  import pt.digitalis.dif.codegen.templates.StageInstanceCGTemplate;
18  import pt.digitalis.dif.dem.Entity;
19  import pt.digitalis.dif.exception.codegen.DIFCodeGenerationException;
20  import pt.digitalis.dif.utils.ObjectFormatter;
21  import pt.digitalis.utils.CodeGenUtil4Javassist;
22  import pt.digitalis.utils.bytecode.exceptions.CodeGenerationException;
23  import pt.digitalis.utils.bytecode.holders.ClassHolder;
24  import pt.digitalis.utils.inspection.exception.ResourceNotFoundException;
25  
26  /**
27   * Holds the information needed to enhance a set of classes.
28   * 
29   * @author Rodrigo Gon�alves <a href="mailto:rgoncalves@digitalis.pt">rgoncalves@digitalis.pt</a><br/>
30   * @created 2007/12/10
31   */
32  public class ClassEnhancementContext {
33  
34      /** The initial map capacity. */
35      final static private int INITIAL_CAPACITY = 2;
36  
37      /** The initial load factor. */
38      final static private float LOAD_FACTOR = 1.0f;
39  
40      /** Holds the enhancements to do on the target classes. */
41      /*
42       * Implementation Note: this hash-map is optimised for the situation we're having here. At most, we will have two
43       * objects to enhance (Stage). By forcing a load factor of 1.0 we're guaranteeing that no rehash (hence no memory
44       * footprint or processing time elapsing) will occur. An initial capacity of 2 and a load factor of 0.75 (default
45       * value) would yield a rehashing to size 4 after the first insertion (HashMap source code defines the rehashing
46       * threshold as: threshold = (int) (initialCapacity * loadFactor);). With the above values we will have a threshold
47       * of exactly 2, and rehashing will occur only when (++size > threshold), hence when the third element is added.
48       */
49      private Map<String, ClassEnhancements> classEnhancements = new HashMap<String, ClassEnhancements>(INITIAL_CAPACITY,
50              LOAD_FACTOR);
51  
52      /** The class ID for the entity to be returned has the entity class */
53      private String entityClassID;
54  
55      /** */
56      private Entity entityType;
57  
58      /** The original annotated POJO. */
59      private ClassHolder theOriginalClass;
60  
61      /**
62       * Builds a class enhancement context from a class holder.
63       * 
64       * @param classHolder
65       *            the class holder for the class to enhance
66       * @throws ResourceNotFoundException
67       *             if the class annotations can't be read
68       * @throws CodeGenerationException
69       *             if the passed class holder isn't a valid DEM entity or the class holder's target semantics is
70       *             incorrect
71       */
72      public ClassEnhancementContext(ClassHolder classHolder) throws ResourceNotFoundException, CodeGenerationException
73      {
74  
75          // Store original annotated POJO
76          this.theOriginalClass = classHolder;
77  
78          // Infer entity type
79          this.entityType = Entity.getEntityTypeFromClass(this.theOriginalClass);
80  
81          // If the entity is not a valid DEM entity throw an exception
82          if (this.entityType == null)
83              throw new CodeGenerationException(this.theOriginalClass.getFQName()
84                      + " is not a valid DEM entity and thus cannot be enhanced! Aborting...");
85  
86          // Set enhancements accordingly to entity type
87  
88          // For stages there will be two enriched classes, the proxy and the instances
89          if (entityType.equals(Entity.STAGE))
90          {
91              this.prepareStageEnhancement();
92          }
93          // For all other entities there will be just one enriched class
94          else
95          {
96              this.prepareNonStageEnhancement();
97          }
98      }
99  
100     /**
101      * Builds a class enhancement context from a class ID.
102      * 
103      * @param originalClassID
104      *            the original annotated class
105      * @throws ResourceNotFoundException
106      *             if the class with the given id can't be found
107      * @throws CodeGenerationException
108      *             if the code can't be compiled
109      */
110     public ClassEnhancementContext(String originalClassID) throws ResourceNotFoundException, CodeGenerationException
111     {
112         this(new ClassHolder(originalClassID));
113     }
114 
115     /**
116      * Adds an enhancement to a given class method.
117      * 
118      * @param className
119      *            the name of the class to add the enhancement to
120      * @param enhancement
121      *            the enhancement to add
122      * @throws DIFCodeGenerationException
123      */
124     public void addEnhancement(String className, ClassMethodEnhancement enhancement) throws DIFCodeGenerationException
125     {
126         try
127         {
128             this.classEnhancements.get(className).addEnhancement(enhancement);
129         }
130         catch (Exception e)
131         {
132             DIFCodeGenerationException codeGenException = new DIFCodeGenerationException(
133                     "Error adding enhancement to class", e);
134             codeGenException.addToExceptionContext("Original Class Name", theOriginalClass.getFQName());
135             codeGenException.addToExceptionContext("Entity ID", entityClassID);
136             codeGenException.addToExceptionContext("Entity Type", entityType);
137             codeGenException.addToExceptionContext("Class Name", className);
138             codeGenException.addToExceptionContext("Enhancement", enhancement);
139 
140             throw codeGenException;
141         }
142     }
143 
144     /**
145      * Adds an enhancement to a given class method. This method should be used for non-stage classes only since it does
146      * provide means for specifying what class is to be enhanced
147      * 
148      * @param methodName
149      *            the name of the method to add the enhancement to
150      * @param enhancement
151      *            the enhancement to add
152      * @throws DIFCodeGenerationException
153      */
154     public void addEnhancement(String methodName, String enhancement) throws DIFCodeGenerationException
155     {
156         if (entityType == Entity.STAGE)
157             addEnhancement(CGAncillaries.STAGE_PROXY_ID, methodName, enhancement);
158         else
159             addEnhancement(CGAncillaries.NON_STAGE_ENRICHED_CLASS_ID, methodName, enhancement);
160     }
161 
162     /**
163      * Adds an enhancement to a given class method.
164      * 
165      * @param className
166      *            the name of the class to add the enhancement to
167      * @param methodName
168      *            the name of the method to add the enhancement to
169      * @param enhancement
170      *            the enhancement to add
171      * @throws DIFCodeGenerationException
172      */
173     public void addEnhancement(String className, String methodName, String enhancement)
174             throws DIFCodeGenerationException
175     {
176         try
177         {
178             this.classEnhancements.get(className).addSource(methodName, enhancement);
179         }
180         catch (Exception e)
181         {
182             DIFCodeGenerationException codeGenException = new DIFCodeGenerationException(
183                     "Error adding enhancement to class", e);
184             codeGenException.addToExceptionContext("Original Class Name", theOriginalClass.getFQName());
185             codeGenException.addToExceptionContext("Entity ID", entityClassID);
186             codeGenException.addToExceptionContext("Entity Type", entityType);
187             codeGenException.addToExceptionContext("Class Name", className);
188             codeGenException.addToExceptionContext("Method Name", methodName);
189             codeGenException.addToExceptionContext("Enhancement", enhancement);
190 
191             throw codeGenException;
192         }
193     }
194 
195     /**
196      * Adds an enhancement to a given class method.
197      * 
198      * @param className
199      *            the name of the class to add the enhancement to
200      * @param methodName
201      *            the name of the method to add the enhancement to
202      * @param terminator
203      *            the finalize code for the method
204      * @throws DIFCodeGenerationException
205      */
206     public void addEnhancementTerminatorCode(String className, String methodName, String terminator)
207             throws DIFCodeGenerationException
208     {
209         try
210         {
211             this.classEnhancements.get(className).setTerminator(methodName, terminator);
212         }
213         catch (Exception e)
214         {
215             DIFCodeGenerationException codeGenException = new DIFCodeGenerationException(
216                     "Error adding enhancement to class", e);
217             codeGenException.addToExceptionContext("Original Class Name", theOriginalClass.getFQName());
218             codeGenException.addToExceptionContext("Entity ID", entityClassID);
219             codeGenException.addToExceptionContext("Entity Type", entityType);
220             codeGenException.addToExceptionContext("Class Name", className);
221             codeGenException.addToExceptionContext("Method Name", methodName);
222             codeGenException.addToExceptionContext("Terminator", terminator);
223 
224             throw codeGenException;
225         }
226     }
227 
228     /**
229      * Commits all the enhancements to a class file and returns the class holder object.
230      * 
231      * @throws ResourceNotFoundException
232      *             if the class can't be found
233      * @throws CodeGenerationException
234      *             if the code can't be compiled
235      */
236     public void commitEnhancements() throws ResourceNotFoundException, CodeGenerationException
237     {
238         for (String className: this.classEnhancements.keySet())
239         {
240             try
241             {
242                 this.classEnhancements.get(className).commitEnhancements();
243             }
244             catch (Exception e)
245             {
246                 DIFCodeGenerationException codeGenException = new DIFCodeGenerationException(
247                         "Error adding enhancement to class", e);
248                 codeGenException.addToExceptionContext("Original Class Name", theOriginalClass.getFQName());
249                 codeGenException.addToExceptionContext("Entity ID", entityClassID);
250                 codeGenException.addToExceptionContext("Entity Type", entityType);
251                 codeGenException.addToExceptionContext("Class Name", className);
252             }
253         }
254     }
255 
256     /**
257      * Inspects the enhancement map to see of the given method has been inserted for enhancement
258      * 
259      * @param methodName
260      *            the name of the method to add the enhancement to
261      * @return T if the method is present
262      */
263     public boolean containsEnhancement(String methodName)
264     {
265         if (entityType == Entity.STAGE)
266             return containsEnhancement(CGAncillaries.STAGE_PROXY_ID, methodName);
267         else
268             return containsEnhancement(CGAncillaries.NON_STAGE_ENRICHED_CLASS_ID, methodName);
269     }
270 
271     /**
272      * Inspects the enhancement map to see of the given method has been inserted for enhancement
273      * 
274      * @param className
275      *            the name of the class to add the enhancement to
276      * @param methodName
277      *            the name of the method to add the enhancement to
278      * @return T if the method is present
279      */
280     public boolean containsEnhancement(String className, String methodName)
281     {
282         return this.classEnhancements.get(className).getMethodEnhancements().containsKey(methodName);
283     }
284 
285     /**
286      * Returns the class enhancements map.
287      * 
288      * @return the class enhancements map.
289      */
290     public Map<String, ClassEnhancements> getClassEnhancements()
291     {
292         return this.classEnhancements;
293     }
294 
295     /**
296      * Return the enriched entity class holder.
297      * 
298      * @return the class holder
299      */
300     public ClassHolder getEntityClass()
301     {
302         return classEnhancements.get(entityClassID).getClassObject();
303     }
304 
305     /**
306      * Return the original POJO class holder.
307      * 
308      * @return the class holder
309      */
310     public ClassHolder getOriginalClassObject()
311     {
312         return this.theOriginalClass;
313     }
314 
315     /**
316      * Returns the original class name.
317      * 
318      * @return the original class name
319      */
320     public String getOriginalClassName()
321     {
322         return this.theOriginalClass.getFQName();
323     }
324 
325     /**
326      * Prepare the class enhancement context for a non-stage entity.
327      * 
328      * @throws CodeGenerationException
329      *             if the Entity is invalid
330      * @throws ResourceNotFoundException
331      *             if the template class can't be found
332      */
333     final private void prepareNonStageEnhancement() throws CodeGenerationException, ResourceNotFoundException
334     {
335         // Create entity class holder
336         this.entityClassID = CGAncillaries.NON_STAGE_ENRICHED_CLASS_ID;
337         ClassHolder enrichedClassHolder = CodeGenUtil4Javassist.createNewClassHolder(this.theOriginalClass.getFQName()
338                 + CGAncillaries.NON_STAGE_ENRICHED_CLASS_ID);
339         try
340         {
341             // Extend stage instance from original stage
342             enrichedClassHolder.setSuperClass(this.theOriginalClass.getFQName());
343         }
344         catch (CannotCompileException cannotCompileException)
345         {
346             throw new CodeGenerationException("Class " + this.theOriginalClass.getFQName()
347                     + " already extends from another class! Unable to compile...", cannotCompileException);
348         }
349 
350         // Copy methods from the appropriate template
351         switch (entityType)
352         {
353             case VALIDATOR:
354                 // No ValidatorCGTemplate class defined in this release
355                 // enrichedClassHolder.copyAllFromClass(new ClassHolder(ValidatorCGTemplate.class.getCanonicalName()));
356                 break;
357             case PROVIDER:
358                 enrichedClassHolder.copyAllFromClass(new ClassHolder(ProviderCGTemplate.class.getCanonicalName()));
359                 break;
360             case APPLICATION:
361                 enrichedClassHolder.copyAllFromClass(new ClassHolder(ApplicationCGTemplate.class.getCanonicalName()));
362                 break;
363             case SERVICE:
364                 enrichedClassHolder.copyAllFromClass(new ClassHolder(ServiceCGTemplate.class.getCanonicalName()));
365                 break;
366             default:
367                 throw new CodeGenerationException("Invalid entity type: " + this.theOriginalClass.getFQName()
368                         + "!! Aborting...");
369         }
370 
371         // Create class enhancements object
372         ClassEnhancements enrichedClassEnhancements = new ClassEnhancements(enrichedClassHolder);
373 
374         // Add class to class enhancement maps
375         classEnhancements.put(CGAncillaries.NON_STAGE_ENRICHED_CLASS_ID, enrichedClassEnhancements);
376     }
377 
378     /**
379      * Prepare the class enhancement context for a stage entity.
380      * 
381      * @throws ResourceNotFoundException
382      *             if any class to copy or to extend from can't be found
383      * @throws CodeGenerationException
384      *             if the original stage POJO already inherits from another class
385      */
386     final private void prepareStageEnhancement() throws CodeGenerationException, ResourceNotFoundException
387     {
388         // Create proxy class holder
389         this.entityClassID = CGAncillaries.STAGE_PROXY_ID;
390         ClassHolder stageProxyClassHolder = CodeGenUtil4Javassist.createNewClassHolder(this.theOriginalClass
391                 .getFQName() + CGAncillaries.STAGE_PROXY_ID);
392         // Copy StageCGTemplate
393         stageProxyClassHolder.copyAllFromClass(new ClassHolder(StageCGTemplate.class.getCanonicalName()));
394         // Create instance class holder
395         ClassEnhancements stageProxyClassEnhancements = new ClassEnhancements(stageProxyClassHolder);
396 
397         // Create stage instance class holder
398         ClassHolder stageInstanceClassHolder = CodeGenUtil4Javassist.createNewClassHolder(this.theOriginalClass
399                 .getFQName() + CGAncillaries.STAGE_INSTANCE_ID);
400 
401         try
402         {
403             // Extend stage instance from original stage
404             stageInstanceClassHolder.setSuperClass(this.theOriginalClass.getFQName());
405         }
406         catch (CannotCompileException cannotCompileException)
407         {
408             throw new CodeGenerationException("Class " + this.theOriginalClass.getFQName()
409                     + " already extends from another class! Unable to compile...", cannotCompileException);
410         }
411         // Copy StageInstanceCGTemplate
412         stageInstanceClassHolder.copyAllFromClass(new ClassHolder(StageInstanceCGTemplate.class.getCanonicalName()));
413 
414         // Create class enhancements object
415         ClassEnhancements stageInstanceClassEnhancements = new ClassEnhancements(stageInstanceClassHolder);
416 
417         // Stage proxys have list builder methods. They will be incremental.
418         stageProxyClassEnhancements.registerMethodAsIncremental(CGAncillaries.STAGE_INJECTED_VIEWS_BUILDER);
419         stageProxyClassEnhancements.registerMethodAsIncremental(CGAncillaries.STAGE_INJECTED_ERRORVIEWS_BUILDER);
420         stageProxyClassEnhancements.registerMethodAsIncremental(CGAncillaries.STAGE_INJECTED_STAGES_BUILDER);
421         stageProxyClassEnhancements.registerMethodAsIncremental(CGAncillaries.STAGE_INJECTED_ERRORSTAGES_BUILDER);
422         stageProxyClassEnhancements.registerMethodAsIncremental(CGAncillaries.STAGE_EVENT_HANDLERS_BUILDER);
423 
424         // Stage instances will have initialization and finalization methods. All will be treated as incremental
425         // methods, as such they must be registered.
426         stageInstanceClassEnhancements
427                 .registerMethodAsIncremental(CGAncillaries.STAGE_INJECTED_ATTRIBUTES_INIT_METHOD_NAME);
428         stageInstanceClassEnhancements.registerMethodAsIncremental(CGAncillaries.STAGE_POSTPROCESSING_METHOD_NAME);
429         stageInstanceClassEnhancements.registerMethodAsIncremental(CGAncillaries.CALL_EXEC_ONEVENT_METHOD);
430         stageInstanceClassEnhancements.registerMethodAsIncremental(CGAncillaries.CALL_EVENT_METHOD);
431 
432         // Add classes to class enhancement maps
433         classEnhancements.put(CGAncillaries.STAGE_PROXY_ID, stageProxyClassEnhancements);
434         classEnhancements.put(CGAncillaries.STAGE_INSTANCE_ID, stageInstanceClassEnhancements);
435     }
436 
437     /**
438      * Adds an enhancement to a given class method.
439      * 
440      * @param className
441      *            the name of the class to add the enhancement to
442      * @param methodName
443      *            the name of the method to add the enhancement to
444      * @param enhancement
445      *            the enhancement to add
446      */
447     public void setEnhancements(String className, String methodName, String enhancement)
448     {
449         this.classEnhancements.get(className).addSource(methodName, enhancement);
450     }
451 
452     /**
453      * @see java.lang.Object#toString()
454      */
455     @Override
456     public String toString()
457     {
458         ObjectFormatter formatter = new ObjectFormatter();
459 
460         formatter.addItem("Original Class Name", this.getOriginalClassName());
461         formatter.addItem("Entity Class Type", this.entityType);
462         formatter.addItem("Entity Class Name", this.entityClassID);
463         formatter.addItem("Classes", this.classEnhancements);
464 
465         return formatter.getFormatedObject();
466     }
467 }