View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.jxr;
20  
21  import java.io.File;
22  import java.io.FileOutputStream;
23  import java.io.OutputStreamWriter;
24  import java.io.Writer;
25  import java.nio.file.Path;
26  import java.nio.file.Paths;
27  import java.util.Map;
28  import java.util.TreeMap;
29  
30  import org.apache.maven.jxr.pacman.ClassType;
31  import org.apache.maven.jxr.pacman.PackageManager;
32  import org.apache.maven.jxr.pacman.PackageType;
33  import org.apache.velocity.Template;
34  import org.apache.velocity.VelocityContext;
35  import org.apache.velocity.app.VelocityEngine;
36  
37  /**
38   * This class creates the navigational pages for jxr's cross-referenced source
39   * files. The navigation is inspired by javadoc, so it should have a familiar feel.
40   *
41   * Creates the following files:
42   * <ul>
43   * <li>{@code index.html}            main index containing the frameset</li>
44   * <li>{@code overview-frame.html}   list of the project's packages              (top left)</li>
45   * <li>{@code allclasses-frame.html} list of all classes in the project          (bottom left)</li>
46   * <li>{@code overview-summary.html} top-level listing of the project's packages (main frame)</li>
47   *
48   * <li>
49   * Package specific:
50   * <ul>
51   * <li>{@code package-summary.html} listing of all classes in this package    (main frame)</li>
52   * <li>{@code package-frame.html}   listing of all classes in this package    (bottom left)</li>
53   * </ul>
54   * </li>
55   * </ul>
56   *
57   * @author <a href="mailto:bellingard@gmail.com">Fabrice Bellingard </a>
58   * @author <a href="mailto:brian@brainslug.org">Brian Leonard</a>
59   */
60  public class DirectoryIndexer {
61      /*
62       * JavaCodeTransform uses this to cross-reference package references
63       * with that package's main summary page.
64       */
65      static final String INDEX = "package-summary.html";
66  
67      /*
68       * Path to the root output directory.
69       */
70      private String root;
71  
72      /*
73       * Package Manager for this project.
74       */
75      private PackageManager packageManager;
76  
77      /*
78       * see the getter/setter docs for these properties
79       */
80      private String outputEncoding;
81  
82      private String templateDir;
83  
84      private String windowTitle;
85  
86      private String docTitle;
87  
88      private String bottom;
89  
90      /**
91       * Constructor for the DirectoryIndexer object
92       *
93       * @param packageManager PackageManager for this project
94       * @param root Path of the root output directory
95       */
96      public DirectoryIndexer(PackageManager packageManager, String root) {
97          this.packageManager = packageManager;
98          this.root = root;
99      }
100 
101     /**
102      * OutputEncoding is the encoding of output files.
103      *
104      * @param outputEncoding output encoding
105      */
106     public void setOutputEncoding(String outputEncoding) {
107         this.outputEncoding = outputEncoding;
108     }
109 
110     /**
111      * Gets the output encoding.
112      * @return output encoding
113      */
114     public String getOutputEncoding() {
115         return outputEncoding;
116     }
117 
118     /**
119      * {@code templateDir} is the location of the jelly template files used
120      * to generate the navigation pages.
121      *
122      * @param templateDir location of the template directory
123      */
124     public void setTemplateDir(String templateDir) {
125         this.templateDir = templateDir;
126     }
127 
128     /**
129      * Gets the template directory.
130      *
131      * @return location of the template directory
132      */
133     public String getTemplateDir() {
134         return templateDir;
135     }
136 
137     /**
138      * {@code windowTitle} is used in the output's &lt;title&gt; tags
139      * see the javadoc documentation for the property of the same name
140      *
141      * @param windowTitle the &lt;title&gt; attribute
142      */
143     public void setWindowTitle(String windowTitle) {
144         this.windowTitle = windowTitle;
145     }
146 
147     /**
148      * Gets the window title.
149      *
150      * @return window title
151      */
152     public String getWindowTitle() {
153         return windowTitle;
154     }
155 
156     /**
157      * {@code docTitle} is used as a page heading for the summary files.<br>
158      * See the javadoc documentation for the property of the same name
159      *
160      * @param docTitle major page heading
161      */
162     public void setDocTitle(String docTitle) {
163         this.docTitle = docTitle;
164     }
165 
166     /**
167      * Gets the major page heading.
168      *
169      * @return major page heading
170      */
171     public String getDocTitle() {
172         return docTitle;
173     }
174 
175     /**
176      * Bottom is a footer for the navigation pages, usually a copyright.<br>
177      * See the javadoc documentation for the property of the same name.
178      *
179      * @param bottom page footer
180      */
181     public void setBottom(String bottom) {
182         this.bottom = bottom;
183     }
184 
185     /**
186      * Gets the footer for the navigation pages.
187      *
188      * @return bottom page footer
189      */
190     public String getBottom() {
191         return bottom;
192     }
193 
194     /**
195      * Does the actual indexing.
196      *
197      * @throws JxrException If something went wrong
198      */
199     public void process() throws JxrException {
200         ProjectInfo info = getProjectInfo();
201 
202         VelocityEngine engine = new VelocityEngine();
203         setProperties(engine);
204         try {
205             engine.init();
206         } catch (Exception e) {
207             throw new JxrException("Error initializing Velocity", e);
208         }
209 
210         VelocityContext context = new VelocityContext();
211         context.put("outputEncoding", getOutputEncoding());
212         context.put("windowTitle", getWindowTitle());
213         context.put("docTitle", getDocTitle());
214         context.put("bottom", getBottom());
215         context.put("info", info);
216 
217         doVelocity("index", root, context, engine);
218         doVelocity("overview-frame", root, context, engine);
219         doVelocity("allclasses-frame", root, context, engine);
220         doVelocity("overview-summary", root, context, engine);
221 
222         for (PackageInfo pkgInfo : info.getAllPackages().values()) {
223             VelocityContext subContext = new VelocityContext(context);
224             subContext.put("pkgInfo", pkgInfo);
225 
226             String outDir = root + '/' + pkgInfo.getDir();
227             doVelocity("package-summary", outDir, subContext, engine);
228             doVelocity("package-frame", outDir, subContext, engine);
229         }
230     }
231 
232     /*
233      * Sets Velocity properties to find templates.
234      */
235     private void setProperties(VelocityEngine engine) {
236         Path templateDirFile = Paths.get(getTemplateDir());
237         if (templateDirFile.isAbsolute()) {
238             // the property has been overridden: need to use a FileResourceLoader
239             engine.setProperty("resource.loaders", "file");
240             engine.setProperty(
241                     "resource.loader.file.class", "org.apache.velocity.runtime.resource.loader.FileResourceLoader");
242             engine.setProperty("resource.loader.file.path", templateDirFile.toString());
243         } else {
244             // use of the default templates
245             engine.setProperty("resource.loaders", "classpath");
246             engine.setProperty(
247                     "resource.loader.classpath.class",
248                     "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
249         }
250     }
251 
252     /*
253      * Generate the HTML file according to the Velocity template
254      */
255     private void doVelocity(String templateName, String outDir, VelocityContext context, VelocityEngine engine)
256             throws JxrException {
257         // output file
258         File file = new File(outDir, templateName + ".html");
259         file.getParentFile().mkdirs();
260 
261         try (Writer writer = new OutputStreamWriter(new FileOutputStream(file), getOutputEncoding())) {
262             // template file
263             StringBuilder templateFile = new StringBuilder();
264             File templateDirFile = new File(getTemplateDir());
265             if (!templateDirFile.isAbsolute()) {
266                 // default templates
267                 templateFile.append(getTemplateDir());
268                 templateFile.append('/');
269             }
270             templateFile.append(templateName);
271             templateFile.append(".vm");
272             Template template = engine.getTemplate(templateFile.toString());
273 
274             // do the merge
275             template.merge(context, writer);
276             writer.flush();
277         } catch (Exception e) {
278             throw new JxrException("Error merging velocity template", e);
279         }
280     }
281 
282     /*
283      * Creates a Map of other Maps containing information about
284      * this project's packages and classes, obtained from the PackageManager.
285      *
286      * allPackages collection of Maps with package info, with the following format
287      *   {name}    package name (e.g., "org.apache.maven.jxr")
288      *   {dir}     package dir relative to the root output dir (e.g., "org/apache/maven/jxr")
289      *   {rootRef} relative link to root output dir (e.g., "../../../../") note trailing slash
290      *   {classes} collection of Maps with class info
291      *      {name}  class name (e.g., "DirectoryIndexer")
292      *      {dir}   duplicate of package {dir}
293      *
294      * allClasses collection of Maps with class info, format as above
295      *
296      */
297     ProjectInfo getProjectInfo() {
298         Map<String, PackageInfo> allPackages = new TreeMap<>();
299         Map<String, ClassInfo> allClasses = new TreeMap<>();
300 
301         for (PackageType pkg : packageManager.getPackageTypes()) {
302             String pkgName = pkg.getName();
303             String pkgDir = pkgName.replace('.', '/');
304             String rootRef = pkgName.replaceAll("[^\\.]+(\\.|$)", "../");
305 
306             // special case for the default package
307             // javadoc doesn't deal with it, but it's easy for us
308             if (pkgName.length() == 0) {
309                 pkgName = "(default package)";
310                 pkgDir = ".";
311                 rootRef = "./";
312             }
313 
314             Map<String, ClassInfo> pkgClasses = new TreeMap<>();
315             for (ClassType clazz : pkg.getClassTypes()) {
316                 String className = clazz.getName();
317 
318                 ClassInfo classInfo = new ClassInfo(className, pkgDir);
319 
320                 classInfo.setFilename(clazz.getFilename());
321 
322                 pkgClasses.put(className, classInfo);
323 
324                 // Adding package name to key in order to ensure classes with identical names in different packages are
325                 // all included.
326                 allClasses.put(className + "#" + pkgName, classInfo);
327             }
328 
329             PackageInfo pkgInfo = new PackageInfo(pkgName, pkgDir);
330             pkgInfo.setClasses(pkgClasses);
331             pkgInfo.setRootRef(rootRef);
332 
333             allPackages.put(pkgName, pkgInfo);
334         }
335 
336         return new ProjectInfo(allPackages, allClasses);
337     }
338 
339     /**
340      *
341      * @author Robert Scholte
342      * @since 3.2.0
343      */
344     public static class ProjectInfo {
345         private final Map<String, PackageInfo> allPackages;
346 
347         private final Map<String, ClassInfo> allClasses;
348 
349         public ProjectInfo(Map<String, PackageInfo> allPackages, Map<String, ClassInfo> allClasses) {
350             this.allPackages = allPackages;
351             this.allClasses = allClasses;
352         }
353 
354         public Map<String, PackageInfo> getAllPackages() {
355             return allPackages;
356         }
357 
358         public Map<String, ClassInfo> getAllClasses() {
359             return allClasses;
360         }
361     }
362 
363     /**
364      *
365      * @author Robert Scholte
366      * @since 3.2.0
367      */
368     public static class PackageInfo {
369         private final String name;
370 
371         private final String dir;
372 
373         Map<String, ClassInfo> classes;
374 
375         private String rootRef;
376 
377         public PackageInfo(String name, String dir) {
378             this.name = name;
379             this.dir = dir;
380         }
381 
382         public String getName() {
383             return name;
384         }
385 
386         public String getDir() {
387             return dir;
388         }
389 
390         public void setClasses(Map<String, ClassInfo> classes) {
391             this.classes = classes;
392         }
393 
394         public Map<String, ClassInfo> getClasses() {
395             return classes;
396         }
397 
398         public void setRootRef(String rootRef) {
399             this.rootRef = rootRef;
400         }
401 
402         public String getRootRef() {
403             return rootRef;
404         }
405     }
406 
407     /**
408      * Holds class information
409      *
410      * @author Robert Scholte
411      * @since 3.2.0
412      */
413     public static class ClassInfo {
414         private final String name;
415 
416         private final String dir;
417 
418         private String filename;
419 
420         public ClassInfo(String name, String dir) {
421             super();
422             this.name = name;
423             this.dir = dir;
424         }
425 
426         public String getName() {
427             return name;
428         }
429 
430         public String getDir() {
431             return dir;
432         }
433 
434         public void setFilename(String filename) {
435             this.filename = filename;
436         }
437 
438         public String getFilename() {
439             return filename;
440         }
441     }
442 }