1 package org.codehaus.mojo.jaxb2.schemageneration.postprocessing.javadoc;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import com.thoughtworks.qdox.JavaProjectBuilder;
23 import com.thoughtworks.qdox.model.JavaAnnotatedElement;
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.JavaPackage;
29 import com.thoughtworks.qdox.model.JavaSource;
30 import org.apache.maven.plugin.logging.Log;
31 import org.codehaus.mojo.jaxb2.schemageneration.postprocessing.javadoc.location.ClassLocation;
32 import org.codehaus.mojo.jaxb2.schemageneration.postprocessing.javadoc.location.FieldLocation;
33 import org.codehaus.mojo.jaxb2.schemageneration.postprocessing.javadoc.location.MethodLocation;
34 import org.codehaus.mojo.jaxb2.schemageneration.postprocessing.javadoc.location.PackageLocation;
35 import org.codehaus.mojo.jaxb2.shared.FileSystemUtilities;
36 import org.codehaus.mojo.jaxb2.shared.Validate;
37
38 import javax.xml.bind.annotation.XmlAttribute;
39 import javax.xml.bind.annotation.XmlElement;
40 import javax.xml.bind.annotation.XmlEnumValue;
41 import javax.xml.bind.annotation.XmlType;
42 import java.io.File;
43 import java.io.IOException;
44 import java.net.URL;
45 import java.util.Collection;
46 import java.util.Collections;
47 import java.util.List;
48 import java.util.Map;
49 import java.util.SortedMap;
50 import java.util.SortedSet;
51 import java.util.TreeMap;
52
53
54
55
56
57
58
59
60
61
62
63 public class JavaDocExtractor {
64
65
66 private JavaProjectBuilder builder;
67 private Log log;
68
69
70
71
72
73
74 public JavaDocExtractor(final Log log) {
75
76
77 Validate.notNull(log, "log");
78
79
80 this.log = log;
81 this.builder = new JavaProjectBuilder();
82 }
83
84
85
86
87
88
89 public void setEncoding(final String encoding) {
90 this.builder.setEncoding(encoding);
91 }
92
93
94
95
96
97
98
99
100 public JavaDocExtractor addSourceFiles(final List<File> sourceCodeFiles) throws IllegalArgumentException {
101
102
103 Validate.notNull(sourceCodeFiles, "addSourceFiles");
104
105
106 for (File current : sourceCodeFiles) {
107 try {
108 builder.addSource(current);
109 } catch (IOException e) {
110 throw new IllegalArgumentException("Could not add file ["
111 + FileSystemUtilities.getCanonicalPath(current) + "]", e);
112 }
113 }
114
115
116 return this;
117 }
118
119
120
121
122
123
124
125
126 public JavaDocExtractor addSourceURLs(final List<URL> sourceCodeURLs) throws IllegalArgumentException {
127
128
129 Validate.notNull(sourceCodeURLs, "sourceCodeURLs");
130
131
132 for (URL current : sourceCodeURLs) {
133 try {
134 builder.addSource(current);
135 } catch (IOException e) {
136 throw new IllegalArgumentException("Could not add URL [" + current.toString() + "]", e);
137 }
138 }
139
140
141 return this;
142 }
143
144
145
146
147
148
149
150 public SearchableDocumentation process() {
151
152
153 final SortedMap<SortableLocation, JavaDocData> dataHolder = new TreeMap<SortableLocation, JavaDocData>();
154 final Collection<JavaSource> sources = builder.getSources();
155
156 if (log.isInfoEnabled()) {
157 log.info("Processing [" + sources.size() + "] java sources.");
158 }
159
160 for (JavaSource current : sources) {
161
162
163 final JavaPackage currentPackage = current.getPackage();
164 final String packageName = currentPackage.getName();
165 addEntry(dataHolder, new PackageLocation(packageName), currentPackage);
166
167 if (log.isDebugEnabled()) {
168 log.debug("Added package-level JavaDoc for [" + packageName + "]");
169 }
170
171 for (JavaClass currentClass : current.getClasses()) {
172
173
174 final String simpleClassName = currentClass.getName();
175 final String classXmlName = getAnnotationAttributeValuleFrom(XmlType.class,
176 "name",
177 currentClass.getAnnotations());
178
179 final ClassLocation classLocation = new ClassLocation(packageName, simpleClassName, classXmlName);
180 addEntry(dataHolder, classLocation, currentClass);
181
182 if (log.isDebugEnabled()) {
183 log.debug("Added class-level JavaDoc for [" + classLocation + "]");
184 }
185
186 for (JavaField currentField : currentClass.getFields()) {
187
188
189 String annotatedXmlName = getAnnotationAttributeValuleFrom(
190 XmlElement.class,
191 "name",
192 currentField.getAnnotations());
193
194 if (annotatedXmlName == null) {
195 annotatedXmlName = getAnnotationAttributeValuleFrom(
196 XmlAttribute.class,
197 "name",
198 currentField.getAnnotations());
199 }
200 if (annotatedXmlName == null) {
201 annotatedXmlName = getAnnotationAttributeValuleFrom(
202 XmlEnumValue.class,
203 "value",
204 currentField.getAnnotations());
205 }
206
207
208 final FieldLocation fieldLocation = new FieldLocation(
209 packageName,
210 simpleClassName,
211 classXmlName,
212 currentField.getName(),
213 annotatedXmlName);
214
215 addEntry(dataHolder, fieldLocation, currentField);
216
217 if (log.isDebugEnabled()) {
218 log.debug("Added field-level JavaDoc for [" + fieldLocation + "]");
219 }
220 }
221
222 for (JavaMethod currentMethod : currentClass.getMethods()) {
223
224
225 String annotatedXmlName = getAnnotationAttributeValuleFrom(
226 XmlElement.class,
227 "name",
228 currentMethod.getAnnotations());
229
230 if (annotatedXmlName == null) {
231 annotatedXmlName = getAnnotationAttributeValuleFrom(
232 XmlAttribute.class,
233 "name",
234 currentMethod.getAnnotations());
235 }
236
237
238 final MethodLocation location = new MethodLocation(packageName,
239 simpleClassName,
240 classXmlName,
241 currentMethod.getName(),
242 annotatedXmlName,
243 currentMethod.getParameters());
244 addEntry(dataHolder, location, currentMethod);
245
246 if (log.isDebugEnabled()) {
247 log.debug("Added method-level JavaDoc for [" + location + "]");
248 }
249 }
250 }
251 }
252
253
254 return new ReadOnlySearchableDocumentation(dataHolder);
255 }
256
257
258
259
260
261
262
263
264
265
266
267
268
269 private static String getAnnotationAttributeValuleFrom(
270 final Class<?> annotationType,
271 final String attributeName,
272 final List<JavaAnnotation> annotations) {
273
274
275
276 final String fullyQualifiedClassName = annotationType.getName();
277
278 JavaAnnotation annotation = null;
279 String toReturn = null;
280
281 if (annotations != null) {
282
283 for (JavaAnnotation current : annotations) {
284 if (current.getType().isA(fullyQualifiedClassName)) {
285 annotation = current;
286 break;
287 }
288 }
289
290 if (annotation != null) {
291
292 final Object nameValue = annotation.getNamedParameter(attributeName);
293
294 if (nameValue != null && nameValue instanceof String) {
295
296 toReturn = ((String) nameValue).trim();
297
298
299 if (toReturn.startsWith("\"") && toReturn.endsWith("\"")) {
300 toReturn = (((String) nameValue).trim()).substring(1, toReturn.length() - 1);
301 }
302 }
303 }
304 }
305
306
307 return toReturn;
308 }
309
310
311
312
313
314 private void addEntry(final SortedMap<SortableLocation, JavaDocData> map,
315 final SortableLocation key,
316 final JavaAnnotatedElement value) {
317
318
319 if (map.containsKey(key)) {
320
321
322 final JavaDocData existing = map.get(key);
323
324
325 if (key instanceof PackageLocation) {
326
327 final boolean emptyExisting = existing.getComment() == null || existing.getComment().isEmpty();
328 final boolean emptyGiven = value.getComment() == null || value.getComment().isEmpty();
329
330 if (emptyGiven) {
331 if (log.isDebugEnabled()) {
332 log.debug("Skipping processing empty Package javadoc from [" + key + "]");
333 }
334 return;
335 } else if (emptyExisting && log.isWarnEnabled()) {
336 log.warn("Overwriting empty Package javadoc from [" + key + "]");
337 }
338 } else {
339 final String given = "[" + value.getClass().getName() + "]: " + value.getComment();
340 throw new IllegalArgumentException("Not processing duplicate SortableLocation [" + key + "]. "
341 + "\n Existing: " + existing
342 + ".\n Given: [" + given + "]");
343 }
344 }
345
346
347
348
349 map.put(key, new JavaDocData(value.getComment(), value.getTags()));
350 }
351
352
353
354
355 private class ReadOnlySearchableDocumentation implements SearchableDocumentation {
356
357
358 private TreeMap<String, SortableLocation> keyMap;
359 private SortedMap<? extends SortableLocation, JavaDocData> valueMap;
360
361 ReadOnlySearchableDocumentation(final SortedMap<SortableLocation, JavaDocData> valueMap) {
362
363
364 this.valueMap = valueMap;
365
366 keyMap = new TreeMap<String, SortableLocation>();
367 for (Map.Entry<SortableLocation, JavaDocData> current : valueMap.entrySet()) {
368
369 final SortableLocation key = current.getKey();
370 keyMap.put(key.getPath(), key);
371 }
372 }
373
374
375
376
377 @Override
378 public SortedSet<String> getPaths() {
379 return Collections.unmodifiableSortedSet(keyMap.navigableKeySet());
380 }
381
382
383
384
385 @Override
386 public JavaDocData getJavaDoc(final String path) {
387
388
389 Validate.notNull(path, "path");
390
391
392 final SortableLocation location = getLocation(path);
393 return (location == null) ? null : valueMap.get(location);
394 }
395
396
397
398
399 @Override
400 @SuppressWarnings("unchecked")
401 public <T extends SortableLocation> T getLocation(final String path) {
402
403
404 Validate.notNull(path, "path");
405
406
407 return (T) keyMap.get(path);
408 }
409
410
411
412
413 @Override
414 @SuppressWarnings("unchecked")
415 public SortedMap<SortableLocation, JavaDocData> getAll() {
416 return (SortedMap<SortableLocation, JavaDocData>) Collections.unmodifiableSortedMap(valueMap);
417 }
418
419
420
421
422 @Override
423 @SuppressWarnings("unchecked")
424 public <T extends SortableLocation> SortedMap<T, JavaDocData> getAll(final Class<T> type) {
425
426
427 Validate.notNull(type, "type");
428
429
430 final SortedMap<T, JavaDocData> toReturn = new TreeMap<T, JavaDocData>();
431 for (Map.Entry<? extends SortableLocation, JavaDocData> current : valueMap.entrySet()) {
432 if (type == current.getKey().getClass()) {
433 toReturn.put((T) current.getKey(), current.getValue());
434 }
435 }
436
437
438 return toReturn;
439 }
440 }
441 }