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