View Javadoc

1   /*
2    * Copyright 2012-2013 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.properties.report;
17  
18  import java.lang.reflect.Method;
19  import java.util.List;
20  
21  import org.apache.commons.lang.ObjectUtils;
22  
23  import com.thoughtworks.qdox.JavaProjectBuilder;
24  import com.thoughtworks.qdox.model.JavaAnnotation;
25  import com.thoughtworks.qdox.model.JavaClass;
26  import com.thoughtworks.qdox.model.JavaField;
27  import com.thoughtworks.qdox.model.JavaMethod;
28  import com.thoughtworks.qdox.model.expression.Expression;
29  
30  import de.smartics.properties.api.core.annotations.PropertyMetaDataMethod;
31  import de.smartics.properties.api.core.domain.PropertyDescriptor;
32  import de.smartics.properties.report.data.PropertyReport;
33  import de.smartics.properties.report.data.PropertyReportItem;
34  import de.smartics.properties.report.data.PropertyReportSet;
35  import de.smartics.properties.report.data.ReportProblem;
36  import de.smartics.properties.report.data.SourceInfo;
37  import de.smartics.properties.report.data.ValueComment;
38  import de.smartics.properties.spi.core.metadata.PropertyMetaDataDefaults;
39  import de.smartics.properties.spi.core.metadata.PropertyMetaDataParser;
40  import de.smartics.properties.spi.core.metadata.projectdoc.ProjectdocAnnotationCollector;
41  import de.smartics.properties.spi.core.metadata.projectdoc.ProjectdocMetaData;
42  import de.smartics.properties.utils.RuntimeUtils;
43  
44  /**
45   * Helper to add report items of a given descriptor type.
46   */
47  class AddReportItemHelper
48  {
49    // ********************************* Fields *********************************
50  
51    // --- constants ------------------------------------------------------------
52  
53    /**
54     * The no parameter signature.
55     */
56    private static final Class<?>[] NO_PARAMS = new Class<?>[0];
57  
58    // --- members --------------------------------------------------------------
59  
60    /**
61     * Helper to instantiate classes.
62     */
63    private final RuntimeUtils runtimeUtils;
64  
65    /**
66     * The builder to access type information.
67     */
68    private final JavaProjectBuilder javaProjectBuilder;
69  
70    /**
71     * The type whose elements are added as report items.
72     */
73    private final JavaClass propertyDescriptorType;
74  
75    /**
76     * The Java class to the type whose elements are added as report items.
77     */
78    private final Class<?> propertySetJavaClass;
79  
80    /**
81     * Handles annotation information of type and type elements.
82     */
83    private final ProjectdocAnnotationCollector projectdocCollector;
84  
85    // ****************************** Initializer *******************************
86  
87    // ****************************** Constructors ******************************
88  
89    AddReportItemHelper(final RuntimeUtils runtimeUtils,
90        final JavaProjectBuilder javaProjectBuilder,
91        final JavaClass propertyDescriptorType) throws ClassNotFoundException
92    {
93      this.runtimeUtils = runtimeUtils;
94      this.javaProjectBuilder = javaProjectBuilder;
95      this.propertyDescriptorType = propertyDescriptorType;
96  
97      this.propertySetJavaClass = runtimeUtils.loadClass(propertyDescriptorType);
98      // TODO: Check if this is good enough since the defaults could be overridden
99      // by a method level annotation of ProjectSet.
100     final PropertyMetaDataDefaults defaults =
101         new PropertyMetaDataDefaults(propertySetJavaClass);
102     this.projectdocCollector =
103         new ProjectdocAnnotationCollector(propertySetJavaClass, defaults);
104   }
105 
106   // ****************************** Inner Classes *****************************
107 
108   // ********************************* Methods ********************************
109 
110   // --- init -----------------------------------------------------------------
111 
112   // --- get&set --------------------------------------------------------------
113 
114   // --- business -------------------------------------------------------------
115 
116   void addReportItems(final PropertyReport report)
117   {
118     addReportSet(report);
119     addReportPropertyItems(report);
120   }
121 
122   private void addReportSet(final PropertyReport report)
123   {
124     final String type = propertyDescriptorType.getFullyQualifiedName();
125     final String comment = propertyDescriptorType.getComment();
126 
127     final PropertyReportSet.Builder builder = new PropertyReportSet.Builder();
128     builder.withType(type);
129     builder.withComment(comment);
130 
131     final PropertyMetaDataParser metaDataParser =
132         PropertyMetaDataParser.createWithoutContextAccess();
133     final String name =
134         metaDataParser.readPropertySetName(propertySetJavaClass);
135     builder.withPropertySetName(name);
136 
137     final ProjectdocMetaData metaData = projectdocCollector.getParentMetadata();
138     builder.with(metaData);
139 
140     final PropertyReportSet reportSet = builder.build();
141     report.handle(reportSet);
142   }
143 
144   private void addReportPropertyItems(final PropertyReport report)
145   {
146     try
147     {
148       final Class<?> type = runtimeUtils.loadClass(propertyDescriptorType);
149       if (type == null)
150       {
151         return;
152       }
153 
154       final List<JavaMethod> methods = propertyDescriptorType.getMethods(true);
155       for (final JavaMethod method : methods)
156       {
157         handleItem(report, type, method);
158       }
159     }
160     catch (final ClassNotFoundException e)
161     {
162       report.addProblem(new ReportProblem("Cannot load class '"
163                                           + propertyDescriptorType.getName()
164                                           + "'.", e));
165     }
166   }
167 
168   private void handleItem(final PropertyReport report, final Class<?> type,
169       final JavaMethod javaMethod)
170   {
171     if (!isPropertyMethod(javaMethod))
172     {
173       return;
174     }
175 
176     final PropertyMetaDataParser metaDataParser =
177         PropertyMetaDataParser.createWithoutContextAccess();
178     final PropertyReportItem.Builder builder = new PropertyReportItem.Builder();
179 
180     final PropertyDescriptor descriptor =
181         readDescriptor(report, type, javaMethod, metaDataParser);
182     builder.with(descriptor);
183 
184     final SourceInfo sourceInfo =
185         new SourceInfo(type.getName(), javaMethod.getName(),
186             javaMethod.getLineNumber());
187     builder.with(sourceInfo);
188 
189     final String comment = javaMethod.getComment();
190     builder.withComment(comment);
191 
192     final ValueComment valueComment = loadValueComment(report, descriptor);
193     builder.with(valueComment);
194 
195     final PropertyReportItem item = builder.build();
196     report.handle(item);
197   }
198 
199   private PropertyDescriptor readDescriptor(final PropertyReport report,
200       final Class<?> type, final JavaMethod javaMethod,
201       final PropertyMetaDataParser metaDataParser)
202   {
203     final String methodName = javaMethod.getName();
204     final Method method = getMethod(report, type, methodName);
205     final PropertyDescriptor descriptor = metaDataParser.readDescriptor(method);
206     return descriptor;
207   }
208 
209   private Method getMethod(final PropertyReport report, final Class<?> type,
210       final String methodName)
211   {
212     try
213     {
214       final Method method = type.getMethod(methodName, NO_PARAMS);
215       return method;
216     }
217     catch (final Exception e)
218     {
219       report.addProblem(new ReportProblem("Cannot locate method '" + methodName
220                                           + "' in class '" + type.getName()
221                                           + "'.", e));
222       return null;
223     }
224   }
225 
226   private static boolean isPropertyMethod(final JavaMethod javaMethod)
227   {
228     final int parameterCount = javaMethod.getParameters().size();
229     if (parameterCount != 0)
230     {
231       return false;
232     }
233 
234     final String marker = PropertyMetaDataMethod.class.getName();
235     for (final JavaAnnotation annotation : javaMethod.getAnnotations())
236     {
237       if (marker.equals(annotation.getType().getName()))
238       {
239         return true;
240       }
241     }
242 
243     final String methodName = javaMethod.getName();
244     final boolean isPropertyMethod =
245         (!(methodName.endsWith(PropertyMetaDataMethod.PROPERTY_KEY) || methodName
246             .endsWith(PropertyMetaDataMethod.PROPERTY_DESCRIPTOR)));
247     return isPropertyMethod;
248   }
249 
250   private ValueComment loadValueComment(final PropertyReport report,
251       final PropertyDescriptor descriptor)
252   {
253     final Class<?> type = descriptor.getType().getType();
254 
255     if (type.isEnum()) // TODO: Constant class?
256     {
257       final String typeName = type.getName();
258       final JavaClass javaClass = javaProjectBuilder.getClassByName(typeName);
259       if (javaClass != null)
260       {
261         return loadValueComment(report, javaClass);
262       }
263       else
264       {
265         report.addProblem(new ReportProblem("Cannot find type class '"
266                                             + typeName + "'."));
267       }
268     }
269 
270     return new ValueComment(null);
271   }
272 
273   private ValueComment loadValueComment(final PropertyReport report,
274       final JavaClass javaClass)
275   {
276     final String comment = javaClass.getComment();
277     final ValueComment valueComment = new ValueComment(comment);
278 
279     for (final JavaField field : javaClass.getEnumConstants())
280     {
281       try
282       {
283         final List<Expression> constantArguments =
284             field.getEnumConstantArguments();
285         final Object instance;
286 
287         if (constantArguments.isEmpty())
288         {
289           instance = field.getName();
290         }
291         else
292         { // TODO: There could be an annotation that specifies the value or the
293           // index of the argument to check. For now we just use the first.
294           final Expression expression = constantArguments.get(0);
295           instance = normalizeExpression(expression);
296         }
297         valueComment.addValueComment(instance, field.getComment());
298       }
299       catch (final Exception e)
300       {
301         report.addProblem(new ReportProblem(
302             "Cannot load field information from type '"
303                 + javaClass.getFullyQualifiedName() + "'."));
304       }
305     }
306 
307     return valueComment;
308   }
309 
310   private static Object normalizeExpression(final Expression expression)
311   {
312     final Object instance;
313     final Object paramValue = expression.getParameterValue();
314     final String valueString = ObjectUtils.toString(paramValue, null);
315     final int length = valueString.length();
316     if (length > 2 && valueString.charAt(0) == '"'
317         && valueString.charAt(length - 1) == '"')
318     {
319       instance = valueString.substring(1, length - 1);
320     }
321     else
322     {
323       instance = expression;
324     }
325     return instance;
326   }
327 
328   // --- object basics --------------------------------------------------------
329 
330 }