View Javadoc
1   package org.codehaus.mojo.jaxb2.schemageneration.postprocessing.javadoc;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import com.thoughtworks.qdox.JavaProjectBuilder;
23  import com.thoughtworks.qdox.model.JavaAnnotatedElement;
24  import com.thoughtworks.qdox.model.JavaClass;
25  import com.thoughtworks.qdox.model.JavaField;
26  import com.thoughtworks.qdox.model.JavaMethod;
27  import com.thoughtworks.qdox.model.JavaPackage;
28  import com.thoughtworks.qdox.model.JavaSource;
29  import org.apache.maven.plugin.logging.Log;
30  import org.codehaus.mojo.jaxb2.schemageneration.postprocessing.javadoc.location.ClassLocation;
31  import org.codehaus.mojo.jaxb2.schemageneration.postprocessing.javadoc.location.FieldLocation;
32  import org.codehaus.mojo.jaxb2.schemageneration.postprocessing.javadoc.location.MethodLocation;
33  import org.codehaus.mojo.jaxb2.schemageneration.postprocessing.javadoc.location.PackageLocation;
34  import org.codehaus.mojo.jaxb2.shared.FileSystemUtilities;
35  import org.codehaus.mojo.jaxb2.shared.Validate;
36  
37  import java.io.File;
38  import java.io.IOException;
39  import java.net.URL;
40  import java.util.Collection;
41  import java.util.Collections;
42  import java.util.List;
43  import java.util.Map;
44  import java.util.SortedMap;
45  import java.util.SortedSet;
46  import java.util.TreeMap;
47  
48  /**
49   * <p>The schemagen tool operates on compiled bytecode, where JavaDoc comments are not present.
50   * However, the javadoc documentation present in java source files is required within the generated
51   * XSD to increase usability and produce an XSD which does not loose out on important usage information.</p>
52   * <p>The JavaDocExtractor is used as a post processor after creating the XSDs within the compilation
53   * unit, and injects XSD annotations into the appropriate XSD elements or types.</p>
54   *
55   * @author <a href="mailto:lj@jguru.se">Lennart J&ouml;relid</a>, jGuru Europe AB
56   * @since 2.0
57   */
58  public class JavaDocExtractor {
59  
60      // Internal state
61      private JavaProjectBuilder builder;
62      private Log log;
63  
64      /**
65       * Creates a JavaDocExtractor wrapping the supplied Maven Log.
66       *
67       * @param log A non-null Log.
68       */
69      public JavaDocExtractor(final Log log) {
70  
71          // Check sanity
72          Validate.notNull(log, "log");
73  
74          // Create internal state
75          this.log = log;
76          this.builder = new JavaProjectBuilder();
77      }
78  
79      /**
80       * Adds the supplied sourceCodeFiles for processing by this JavaDocExtractor.
81       *
82       * @param sourceCodeFiles The non-null List of source code files to add.
83       * @return This JavaDocExtractor, for call chaining.
84       * @throws IllegalArgumentException If any of the given sourceCodeFiles could not be read properly.
85       */
86      public JavaDocExtractor addSourceFiles(final List<File> sourceCodeFiles) throws IllegalArgumentException {
87  
88          // Check sanity
89          Validate.notNull(sourceCodeFiles, "addSourceFiles");
90  
91          // Add the files.
92          for (File current : sourceCodeFiles) {
93              try {
94                  builder.addSource(current);
95              } catch (IOException e) {
96                  throw new IllegalArgumentException("Could not add file ["
97                          + FileSystemUtilities.getCanonicalPath(current) + "]", e);
98              }
99          }
100 
101         // All done.
102         return this;
103     }
104 
105     /**
106      * Adds the supplied sourceCodeFiles for processing by this JavaDocExtractor.
107      *
108      * @param sourceCodeURLs The non-null List of source code URLs to add.
109      * @return This JavaDocExtractor, for call chaining.
110      * @throws IllegalArgumentException If any of the given sourceCodeURLs could not be read properly.
111      */
112     public JavaDocExtractor addSourceURLs(final List<URL> sourceCodeURLs) throws IllegalArgumentException {
113 
114         // Check sanity
115         Validate.notNull(sourceCodeURLs, "sourceCodeURLs");
116 
117         // Add the URLs
118         for (URL current : sourceCodeURLs) {
119             try {
120                 builder.addSource(current);
121             } catch (IOException e) {
122                 throw new IllegalArgumentException("Could not add URL [" + current.toString() + "]", e);
123             }
124         }
125 
126         // All done
127         return this;
128     }
129 
130     /**
131      * Processes all supplied Java source Files and URLs to extract JavaDocData for all ClassLocations from which
132      * JavaDoc has been collected.
133      *
134      * @return A SearchableDocumentation relating SortableLocations and their paths to harvested JavaDocData.
135      */
136     public SearchableDocumentation process() {
137 
138         // Start processing.
139         final SortedMap<SortableLocation, JavaDocData> dataHolder = new TreeMap<SortableLocation, JavaDocData>();
140         final Collection<JavaSource> sources = builder.getSources();
141 
142         if (log.isInfoEnabled()) {
143             log.info("Processing [" + sources.size() + "] java sources.");
144         }
145 
146         for (JavaSource current : sources) {
147 
148             // Add the package-level JavaDoc
149             final JavaPackage currentPackage = current.getPackage();
150             final String packageName = currentPackage.getName();
151             addEntry(dataHolder, new PackageLocation(packageName), currentPackage);
152 
153             if (log.isDebugEnabled()) {
154                 log.debug("Added package-level JavaDoc for [" + packageName + "]");
155             }
156 
157             for (JavaClass currentClass : current.getClasses()) {
158 
159                 // Add the class-level JavaDoc
160                 final String simpleClassName = currentClass.getName();
161                 final ClassLocation classLocation = new ClassLocation(packageName, simpleClassName);
162                 addEntry(dataHolder, classLocation, currentClass);
163 
164                 if (log.isDebugEnabled()) {
165                     log.debug("Added class-level JavaDoc for [" + classLocation + "]");
166                 }
167 
168                 for (JavaField currentField : currentClass.getFields()) {
169 
170                     // Add the field-level JavaDoc
171                     final FieldLocation fieldLocation = new FieldLocation(
172                             packageName,
173                             simpleClassName,
174                             currentField.getName());
175 
176                     addEntry(dataHolder, fieldLocation, currentField);
177 
178                     if (log.isDebugEnabled()) {
179                         log.debug("Added field-level JavaDoc for [" + fieldLocation + "]");
180                     }
181                 }
182 
183                 for (JavaMethod currentMethod : currentClass.getMethods()) {
184 
185                     // Add the method-level JavaDoc
186                     final MethodLocation location = new MethodLocation(packageName,
187                             simpleClassName,
188                             currentMethod.getName(),
189                             currentMethod.getParameters());
190                     addEntry(dataHolder, location, currentMethod);
191 
192                     if (log.isDebugEnabled()) {
193                         log.debug("Added method-level JavaDoc for [" + location + "]");
194                     }
195                 }
196             }
197         }
198 
199         // All done.
200         return new ReadOnlySearchableDocumentation(dataHolder);
201     }
202 
203     //
204     // Private helpers
205     //
206 
207     private void addEntry(final SortedMap<SortableLocation, JavaDocData> map,
208                           final SortableLocation key,
209                           final JavaAnnotatedElement value) {
210 
211         // Check sanity
212         if (map.containsKey(key)) {
213 
214             // Get something to compare with
215             final JavaDocData existing = map.get(key);
216 
217             // Is this an empty package-level documentation?
218             if (key instanceof PackageLocation) {
219 
220                 final boolean emptyExisting = existing.getComment() == null || existing.getComment().isEmpty();
221                 final boolean emptyGiven = value.getComment() == null || value.getComment().isEmpty();
222 
223                 if (emptyGiven) {
224                     if (log.isDebugEnabled()) {
225                         log.debug("Skipping processing empty Package javadoc from [" + key + "]");
226                     }
227                     return;
228                 } else if (emptyExisting) {
229                     if (log.isWarnEnabled()) {
230                         log.warn("Overwriting empty Package javadoc from [" + key + "]");
231                     }
232                 }
233             } else {
234                 final String given = "[" + value.getClass().getName() + "]: " + value.getComment();
235                 throw new IllegalArgumentException("Not processing duplicate SortableLocation [" + key + "]. "
236                         + "\n Existing: " + existing
237                         + ".\n Given: [" + given + "]");
238             }
239         }
240 
241         // Validate.isTrue(!map.containsKey(key), "Found duplicate SortableLocation [" + key + "] in map. "
242         //         + "Current map keySet: " + map.keySet() + ". Got comment: [" + value.getComment() + "]");
243 
244         map.put(key, new JavaDocData(value.getComment(), value.getTags()));
245     }
246 
247     /**
248      * Standard read-only SearchableDocumentation implementation.
249      */
250     class ReadOnlySearchableDocumentation implements SearchableDocumentation {
251 
252         // Internal state
253         private TreeMap<String, SortableLocation> keyMap;
254         private SortedMap<? extends SortableLocation, JavaDocData> valueMap;
255 
256         ReadOnlySearchableDocumentation(final SortedMap<SortableLocation, JavaDocData> valueMap) {
257 
258             // Create internal state
259             this.valueMap = valueMap;
260 
261             keyMap = new TreeMap<String, SortableLocation>();
262             for (Map.Entry<SortableLocation, JavaDocData> current : valueMap.entrySet()) {
263 
264                 final SortableLocation key = current.getKey();
265                 keyMap.put(key.getPath(), key);
266             }
267         }
268 
269         /**
270          * {@inheritDoc}
271          */
272         @Override
273         public SortedSet<String> getPaths() {
274             return Collections.unmodifiableSortedSet(keyMap.navigableKeySet());
275         }
276 
277         /**
278          * {@inheritDoc}
279          */
280         @Override
281         public JavaDocData getJavaDoc(final String path) {
282 
283             // Check sanity
284             Validate.notNull(path, "path");
285 
286             // All done.
287             final SortableLocation location = getLocation(path);
288             return (location == null) ? null : valueMap.get(location);
289         }
290 
291         /**
292          * {@inheritDoc}
293          */
294         @Override
295         @SuppressWarnings("unchecked")
296         public <T extends SortableLocation> T getLocation(final String path) {
297 
298             // Check sanity
299             Validate.notNull(path, "path");
300 
301             // All done
302             return (T) keyMap.get(path);
303         }
304 
305         /**
306          * {@inheritDoc}
307          */
308         @Override
309         @SuppressWarnings("unchecked")
310         public SortedMap<SortableLocation, JavaDocData> getAll() {
311             return (SortedMap<SortableLocation, JavaDocData>) Collections.unmodifiableSortedMap(valueMap);
312         }
313 
314         /**
315          * {@inheritDoc}
316          */
317         @Override
318         @SuppressWarnings("unchecked")
319         public <T extends SortableLocation> SortedMap<T, JavaDocData> getAll(final Class<T> type) {
320 
321             // Check sanity
322             Validate.notNull(type, "type");
323 
324             // Filter the valueMap.
325             final SortedMap<T, JavaDocData> toReturn = new TreeMap<T, JavaDocData>();
326             for (Map.Entry<? extends SortableLocation, JavaDocData> current : valueMap.entrySet()) {
327                 if (type == current.getKey().getClass()) {
328                     toReturn.put((T) current.getKey(), current.getValue());
329                 }
330             }
331 
332             // All done.
333             return toReturn;
334         }
335     }
336 }