View Javadoc

1   /*
2    * Copyright 2007-2011 smartics, Kronseder & Reiner GmbH
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package de.smartics.exceptions.report.message;
17  
18  import java.io.File;
19  import java.lang.reflect.Field;
20  import java.util.HashMap;
21  import java.util.List;
22  import java.util.Map;
23  
24  import org.apache.commons.logging.Log;
25  import org.apache.commons.logging.LogFactory;
26  
27  import com.sun.javadoc.AnnotationDesc;
28  import com.sun.javadoc.AnnotationTypeDoc;
29  import com.sun.javadoc.ClassDoc;
30  import com.sun.javadoc.Doc;
31  import com.sun.javadoc.FieldDoc;
32  import com.sun.javadoc.AnnotationDesc.ElementValuePair;
33  
34  import de.smartics.analysis.javadoc.CollectorContext;
35  import de.smartics.analysis.javadoc.DefaultJavadocCollector;
36  import de.smartics.analysis.javadoc.JavadocCollector;
37  import de.smartics.analysis.javadoc.JavadocException;
38  import de.smartics.analysis.javadoc.filter.AcceptAllFilter;
39  import de.smartics.analysis.javadoc.runtime.RuntimeUtils;
40  import de.smartics.exceptions.i18n.message.MessageParam;
41  import de.smartics.exceptions.i18n.message.MessageParamParser;
42  import de.smartics.exceptions.i18n.message.MessageType;
43  import de.smartics.exceptions.i18n.message.ParentMessageParam;
44  import de.smartics.exceptions.i18n.message.ParseException;
45  import de.smartics.exceptions.i18n.message.UsedBy;
46  import de.smartics.exceptions.i18n.message.MessageParamParser.MessageParamInfo;
47  import de.smartics.report.conf.ProjectConfiguration;
48  
49  /**
50   * Utilities to read place holder information from a class file.
51   *
52   * @author <a href="mailto:robert.reiner@smartics.de">Robert Reiner</a>
53   * @version $Revision:591 $
54   */
55  public final class PlaceHolderHandler
56  {
57    // ********************************* Fields *********************************
58  
59    // --- constants ------------------------------------------------------------
60  
61    /**
62     * Reference to the logger for this class.
63     */
64    private static final Log LOG = LogFactory.getLog(PlaceHolderHandler.class);
65  
66    // --- members --------------------------------------------------------------
67  
68    /**
69     * The project configuration.
70     */
71    private final ProjectConfiguration<?> config;
72  
73    /**
74     * The class documentation cache.
75     */
76    private final Map<Class<?>, ClassDoc> cache =
77        new HashMap<Class<?>, ClassDoc>();
78  
79    // ****************************** Initializer *******************************
80  
81    // ****************************** Constructors ******************************
82  
83    /**
84     * Utility class pattern.
85     *
86     * @param config the project configuration.
87     */
88    public PlaceHolderHandler(final ProjectConfiguration<?> config)
89    {
90      this.config = config;
91    }
92  
93    // ****************************** Inner Classes *****************************
94  
95    /**
96     * Reads the {@link UsedBy} annotation from the field documentation and
97     * analyzes the referenced class for place holder information.
98     *
99     * @param fieldDoc the field to analyze.
100    * @return the place holder descriptions, the empty map if none has been found
101    *         or the class containing the place holder information cannot be
102    *         analyzed (a log message will be logged at WARN level).
103    * @throws NullPointerException if <code>fieldDoc</code> is <code>null</code>.
104    */
105   public PlaceHolderInfo readPlaceholderDesc(final ClassLoader classLoader,
106       final FieldDoc fieldDoc)
107   {
108     final AnnotationDesc[] annDescs = fieldDoc.annotations();
109     final String usedByClassName = UsedBy.class.getName();
110     for (AnnotationDesc annDesc : annDescs)
111     {
112       final AnnotationTypeDoc annTypeDoc = annDesc.annotationType();
113       if (usedByClassName.equals(annTypeDoc.qualifiedName()))
114       {
115         final ElementValuePair[] pairs = annDesc.elementValues();
116         for (ElementValuePair pair : pairs)
117         {
118           final String name = pair.element().name();
119           if ("value".equals(name))
120           {
121             final ClassDoc referencedClassDoc =
122                 (ClassDoc) pairs[0].value().value();
123             try
124             {
125               final Class<?> referencedClass =
126                   (Class<?>) RuntimeUtils.loadClass(classLoader,
127                       referencedClassDoc);
128               final PlaceHolderInfo placeHolderInfo =
129                   readPlaceholderDesc(referencedClass);
130               return placeHolderInfo;
131             }
132             catch (final Exception e)
133             {
134               if (LOG.isWarnEnabled())
135               {
136                 LOG.warn(
137                     "Cannot load class '" + referencedClassDoc.qualifiedName()
138                         + "' to analyse " + " place holders of field '"
139                         + fieldDoc.qualifiedName() + "'.", e);
140               }
141             }
142           }
143         }
144       }
145     }
146     return PlaceHolderInfo.EMPTY;
147   }
148 
149   private PlaceHolderInfo readPlaceholderDesc(final Class<?> referencedClass)
150   {
151     final PlaceHolderInfo placeHolderInfo = new PlaceHolderInfo();
152     addParentMessageParam(placeHolderInfo, referencedClass);
153     addMessageParam(placeHolderInfo, referencedClass);
154     return placeHolderInfo;
155   }
156 
157   private void addParentMessageParam(final PlaceHolderInfo placeHolderInfo,
158       final Class<?> referencedClass)
159   {
160     final ParentMessageParam messageParam =
161         referencedClass.getAnnotation(ParentMessageParam.class);
162     if (messageParam != null)
163     {
164       addMessageParam(placeHolderInfo, referencedClass, messageParam);
165     }
166   }
167 
168   private void addMessageParam(final PlaceHolderInfo placeHolderInfo,
169       final Class<?> referencedClass)
170   {
171     Class<?> currentClass = referencedClass;
172     do
173     {
174       final Field[] fields = currentClass.getDeclaredFields();
175       for (final Field field : fields)
176       {
177         final MessageParam messageParam =
178             field.getAnnotation(MessageParam.class);
179         if (messageParam != null)
180         {
181           addMessageParam(placeHolderInfo, referencedClass, field, messageParam);
182         }
183       }
184 
185       currentClass = currentClass.getSuperclass();
186     }
187     while (currentClass != null && currentClass != Exception.class);
188   }
189 
190   private void addMessageParam(final PlaceHolderInfo placeHolderInfo,
191       final Class<?> referencedClass, final Field field,
192       final MessageParam messageParam) throws ParseException
193   {
194     final String name = field.getName();
195     addMessageParam(placeHolderInfo, referencedClass, name, messageParam);
196   }
197 
198   private void addMessageParam(final PlaceHolderInfo placeHolderInfo,
199       final Class<?> referencedClass, final String name,
200       final MessageParam messageParam) throws ParseException
201   {
202     addMessageParam(placeHolderInfo, referencedClass, name, null,
203         messageParam.value());
204     addMessageParam(placeHolderInfo, referencedClass, name, MessageType.TITLE,
205         messageParam.headerParamIndex());
206     addMessageParam(placeHolderInfo, referencedClass, name,
207         MessageType.SUMMARY, messageParam.summaryParamIndex());
208     addMessageParam(placeHolderInfo, referencedClass, name,
209         MessageType.DETAILS, messageParam.detailsParamIndex());
210     addMessageParam(placeHolderInfo, referencedClass, name,
211         MessageType.IMPLICATIONS_ON_CURRENT_TASK,
212         messageParam.implicationsParamIndex());
213     addMessageParam(placeHolderInfo, referencedClass, name,
214         MessageType.WHAT_TO_DO_NOW, messageParam.todoParamIndex());
215     addMessageParam(placeHolderInfo, referencedClass, name, MessageType.URL,
216         messageParam.urlParamIndex());
217   }
218 
219   private void addMessageParam(final PlaceHolderInfo placeHolderInfo,
220       final Class<?> referencedClass, final ParentMessageParam messageParam)
221     throws ParseException
222   {
223     addParentMessageParam(placeHolderInfo, referencedClass, null,
224         messageParam.value());
225     addParentMessageParam(placeHolderInfo, referencedClass, MessageType.TITLE,
226         messageParam.headerParamIndex());
227     addParentMessageParam(placeHolderInfo, referencedClass,
228         MessageType.SUMMARY, messageParam.summaryParamIndex());
229     addParentMessageParam(placeHolderInfo, referencedClass,
230         MessageType.DETAILS, messageParam.detailsParamIndex());
231     addParentMessageParam(placeHolderInfo, referencedClass,
232         MessageType.IMPLICATIONS_ON_CURRENT_TASK,
233         messageParam.implicationsParamIndex());
234     addParentMessageParam(placeHolderInfo, referencedClass,
235         MessageType.WHAT_TO_DO_NOW, messageParam.todoParamIndex());
236     addParentMessageParam(placeHolderInfo, referencedClass, MessageType.URL,
237         messageParam.urlParamIndex());
238   }
239 
240   private void addMessageParam(final PlaceHolderInfo placeHolderInfo,
241       final Class<?> referencedClass, final String name,
242       final MessageType messageType, final String value) throws ParseException,
243     NullPointerException
244   {
245     final List<MessageParamInfo> paramInfos = MessageParamParser.parse(value);
246     addInfos(placeHolderInfo, referencedClass, name, messageType, paramInfos);
247   }
248 
249   private void addInfos(final PlaceHolderInfo placeHolderInfo,
250       final Class<?> referencedClass, final String name,
251       final MessageType messageType, final List<MessageParamInfo> paramInfos)
252   {
253     for (final MessageParamInfo paramInfo : paramInfos)
254     {
255       final int placeholderIndex = paramInfo.getIndex();
256       final String ognlPath = paramInfo.getOgnlPath();
257 
258       final String description = readDescription(referencedClass, name);
259       final PlaceHolderDescId id =
260           new PlaceHolderDescId(placeholderIndex, messageType);
261       final PlaceHolderDesc desc =
262           new PlaceHolderDesc(id, name, ognlPath, description);
263       placeHolderInfo.putPlaceHolderDesc(desc);
264     }
265   }
266 
267   private String readDescription(final Class<?> clazz, final String fieldName)
268   {
269     if (clazz != null)
270     {
271       final Class<?> classWithParam = getClassWithParam(clazz, fieldName);
272       if (classWithParam != null)
273       {
274         final ClassDoc classDoc = readClassDoc(classWithParam);
275         if (classDoc != null)
276         {
277           for (FieldDoc fieldDoc : classDoc.fields())
278           {
279             if (fieldName.equals(fieldDoc.name()))
280             {
281               final String description = fieldDoc.commentText();
282               return description;
283             }
284           }
285         }
286       }
287     }
288     return null;
289   }
290 
291   private ClassDoc readClassDoc(final Class<?> clazz)
292   {
293     ClassDoc classDoc = cache.get(clazz);
294     if (classDoc == null && !cache.containsKey(clazz))
295     {
296       final String fileRepresentation = calculateSourceFile(clazz);
297       if (fileRepresentation != null)
298       {
299         final Map<String, String> args = new HashMap<String, String>();
300         args.put(fileRepresentation, "javadoc-arg");
301         final CollectorContext context =
302             new CollectorContext(new AcceptAllFilter(), clazz.getClassLoader());
303         final JavadocCollector collector =
304             new DefaultJavadocCollector("placeholder", args, context);
305         try
306         {
307           final List<Doc> docs = collector.collect();
308           classDoc = getClassDoc(clazz, docs);
309           cache.put(clazz, classDoc);
310         }
311         catch (final JavadocException e)
312         {
313           if (LOG.isWarnEnabled())
314           {
315             LOG.warn("Cannot collect Javadoc information for class '"
316                      + clazz.getName() + "'.");
317           }
318           cache.put(clazz, null);
319         }
320       }
321       else
322       {
323         if (LOG.isDebugEnabled())
324         {
325           LOG.debug("Cannot find sources for class '" + clazz.getName()
326                     + "' in " + config.getSourceRootDirectoryNames());
327         }
328       }
329     }
330     return classDoc;
331   }
332 
333   private String calculateSourceFile(final Class<?> clazz)
334   {
335     final String suffix = clazz.getName().replace('.', '/') + ".java";
336     for (String prefix : config.getSourceRootDirectoryNames())
337     {
338       final File file = new File(prefix, suffix);
339       if (file.exists())
340       {
341         return file.getAbsolutePath();
342       }
343     }
344     return null;
345   }
346 
347   private ClassDoc getClassDoc(final Class<?> clazz, final List<Doc> docs)
348   {
349     final String qualifiedName = clazz.getName();
350     for (Doc doc : docs)
351     {
352       if (doc.isClass())
353       {
354         final ClassDoc classDoc = (ClassDoc) doc;
355         if (qualifiedName.equals(classDoc.qualifiedName()))
356         {
357           return classDoc;
358         }
359       }
360     }
361     return null;
362   }
363 
364   private void addParentMessageParam(final PlaceHolderInfo placeHolderInfo,
365       final Class<?> referencedClass, final MessageType messageType,
366       final String value) throws ParseException, NullPointerException
367   {
368     final Map<String, List<MessageParamInfo>> paramInfoMap =
369         MessageParamParser.parseParentMessageParam(value);
370 
371     for (final Map.Entry<String, List<MessageParamInfo>> paramInfoEntry : paramInfoMap
372         .entrySet())
373     {
374       final String name = paramInfoEntry.getKey();
375       final List<MessageParamInfo> paramInfos = paramInfoEntry.getValue();
376 
377       addInfos(placeHolderInfo, referencedClass, name, messageType, paramInfos);
378     }
379   }
380 
381   private Class<?> getClassWithParam(final Class<?> clazz, final String name)
382   {
383     Class<?> currentClass = clazz;
384     do
385     {
386       try
387       {
388         currentClass.getDeclaredField(name);
389         return currentClass;
390       }
391       catch (final Exception e)
392       {
393         // continue with next class...
394       }
395       currentClass = currentClass.getSuperclass();
396     }
397     while (currentClass != null && currentClass != Object.class);
398     return null;
399   }
400 
401   // --- object basics --------------------------------------------------------
402 
403 }