View Javadoc
1   
2   package net.sf.dtddoc.maven;
3   
4   import java.io.File;
5   import java.io.IOException;
6   import java.util.ArrayList;
7   import java.util.Collections;
8   import java.util.HashSet;
9   import java.util.Iterator;
10  import java.util.List;
11  import java.util.Locale;
12  import java.util.Set;
13  
14  import DTDDoc.DTDCommenter;
15  
16  import org.apache.maven.doxia.sink.Sink;
17  import org.apache.maven.doxia.siterenderer.Renderer;
18  import org.apache.maven.plugins.annotations.Component;
19  import org.apache.maven.plugins.annotations.Mojo;
20  import org.apache.maven.plugins.annotations.Parameter;
21  import org.apache.maven.project.MavenProject;
22  import org.apache.maven.reporting.AbstractMavenReport;
23  import org.apache.maven.reporting.MavenReportException;
24  import org.codehaus.plexus.util.FileUtils;
25  import org.codehaus.plexus.util.StringUtils;
26  
27  /**
28   * DTDDoc Maven2 plugin, generates DTDDoc report.
29   */
30  @Mojo( name = "dtddoc", threadSafe = true )
31  public class DTDDocReport extends AbstractMavenReport {
32    /**
33     * Specifies the destination directory where DTDDoc saves the generated HTML files.
34     */
35    @Parameter( defaultValue = "${project.build.directory}/dtddoc/", required = true )
36    private File outputDirectory;
37  
38    /**
39     * Specifies the source directory where DTDDoc searches for DTDs.
40     */
41    @Parameter( defaultValue = "src/main/resources", required = true )
42    private File sourceDirectory;
43  
44    /**
45     * List of patterns used to specify the files that should be included in DTD documentation.
46     * When not specified, the default includes will be <code>**&#47;*.dtd</code>.
47     */
48    @Parameter
49    private String[] includes;
50  
51    /**
52     * List of patterns used to specify the files that should be excluded in DTD documentation.
53     * When not specified, the default excludes will be empty.
54     */
55    @Parameter
56    private String[] excludes;
57  
58    /**
59     * Tells if the content of the <code>@hidden</code> tags must be part of the documentation
60     */
61    @Parameter( property = "dtddoc.hidden", defaultValue = "false", required = true )
62    private boolean showHiddenTags = false;
63  
64    /**
65     * Tells if the content of the <code>@fixme</code> tags must be part of the documentation
66     */
67    @Parameter( property = "dtddoc.fixme", defaultValue = "true", required = true )
68    private boolean showFixmeTags = true;
69  
70    /**
71     * In case you have comments that start with three hyphens, this option will tell DTDDoc
72     * to interpret them as regular double hyphens. This was introduced to support NetBeans comments.
73     */
74    @Parameter( property = "dtddoc.getAroundNetBeansComments", defaultValue = "true", required = true )
75    private boolean getAroundNetBeansComments = true;
76  
77    /**
78     * Name of the documentation: it will appear at the top of the index.
79     */
80    @Parameter( property = "dtddoc.title" )
81    private String docTitle = "";
82  
83    /**
84     * The stylesheet to use to render the documentation. This stylesheet will be copied into the documentation.
85     * If not specified, DTDDoc's default stylesheet is used.
86     */
87    @Parameter( property = "dtddoc.styleSheet" )
88    private File styleSheet = null;
89  
90    /**
91     * Doxia Site Renderer.
92     */
93    @Component
94    protected Renderer siteRenderer;
95  
96    /**
97     * Maven project
98     */
99    @Parameter( defaultValue = "${project}", required = true, readonly = true )
100   private MavenProject project;
101 
102   public String getName(Locale locale) {
103     return "DTDDoc";
104   }
105 
106   public String getDescription(Locale locale) {
107     return "DTD documentation";
108   }
109 
110   /**
111    * @see org.apache.maven.reporting.MavenReport#getOutputName()
112    */
113   public String getOutputName() {
114     return "dtddoc/index";
115   }
116 
117   /**
118    * @see org.apache.maven.reporting.AbstractMavenReport#getOutputDirectory()
119    */
120   protected String getOutputDirectory() {
121     return outputDirectory.getAbsoluteFile().toString();
122   }
123 
124   /**
125    * @see org.apache.maven.reporting.AbstractMavenReport#getProject()
126    */
127   protected MavenProject getProject() {
128     return project;
129   }
130 
131   /**
132    * @see org.apache.maven.reporting.AbstractMavenReport#getSiteRenderer()
133    */
134   protected Renderer getSiteRenderer() {
135     return siteRenderer;
136   }
137 
138   /**
139    * @see org.apache.maven.reporting.MavenReport#generate(org.codehaus.doxia.sink.Sink, java.util.Locale)
140    */
141   public void generate(Sink sink, Locale locale) throws MavenReportException {
142     executeReport(locale);
143   }
144 
145   private List getDtdFiles() {
146     if (! (sourceDirectory.exists() && sourceDirectory.isDirectory())) {
147       return new ArrayList();
148     }
149 
150     String includesAsString = "**/*.dtd";
151     if (includes != null) {
152       includesAsString = StringUtils.join(includes, "," );
153     }
154 
155     String excludesAsString = "";
156     if (excludes != null) {
157       excludesAsString = StringUtils.join(excludes, "," );
158     }
159 
160     try {
161       return FileUtils.getFiles(sourceDirectory, includesAsString, excludesAsString, true);
162     } catch (IOException ioe) {
163       getLog().warn("unable to determine which DTDs to process", ioe);
164     }
165     return new ArrayList();
166   }
167 
168   protected void executeReport(Locale locale) throws MavenReportException {
169     Set dtds = new HashSet(getDtdFiles());
170 
171     File dtddocDirectory = getReportOutputDirectory();
172     if (!dtddocDirectory.getAbsolutePath().equals(getOutputDirectory())) {
173       // we're in site-embedded report mode, so Doxia has set the
174       // reportOutputDirectory to the basedir of the site : append 'dtddoc'.
175       dtddocDirectory = new File(dtddocDirectory, "dtddoc");
176     }
177     dtddocDirectory.mkdirs();
178 
179     try {
180       getLog().info("documenting " + dtds.size() + " DTD(s), show @hidden tags: " + showHiddenTags + ", show @fixme tags: " + showFixmeTags);
181       File commonBase = getCommonBase(sourceDirectory, dtds);
182       if (! sourceDirectory.equals(commonBase)) {
183         getLog().info("sourceDirectory optimized as " + commonBase.getPath());
184       }
185       DTDCommenter commenter = new DTDCommenter(new MavenLogger(getLog()));
186       commenter
187           .commentDTDs(dtds, commonBase, dtddocDirectory, showHiddenTags, showFixmeTags, getAroundNetBeansComments, docTitle, styleSheet);
188     } catch (IOException ioe) {
189       throw new MavenReportException("error while generating DTDDoc reports : " + ioe.getMessage(), ioe);
190     } catch (Exception e) {
191       throw new MavenReportException("error while generating DTDDoc reports : " + e.getMessage(), e);
192     }
193   }
194 
195   public boolean isExternalReport() {
196     return true;
197   }
198 
199   /**
200    * @see org.apache.maven.reporting.MavenReport#canGenerateReport()
201    */
202   public boolean canGenerateReport() {
203     return getDtdFiles().size() > 0;
204   }
205 
206   protected static File[] getFileHierarchy(File from, File to) {
207     List hierarchy = new ArrayList();
208     hierarchy.add(to);
209     while (! to.equals(from)) {
210       to = to.getParentFile();
211       hierarchy.add(to);
212     }
213     Collections.reverse(hierarchy);
214     File[] files = new File[hierarchy.size()];
215     return (File[])hierarchy.toArray(files);
216   }
217 
218   protected File getCommonBase(File sourceDirectory, Set files) {
219     if (files.size() == 1) {
220       // if there is only one file, its directory is the "common" base
221       return ((File)files.iterator().next()).getParentFile();
222     }
223 
224     File[] base = null;
225     for (Iterator iter = files.iterator(); iter.hasNext(); ) {
226       File current = ((File)iter.next()).getParentFile();
227       File[] currentHierarchy = getFileHierarchy(sourceDirectory, current);
228       if (base == null) {
229         base = currentHierarchy;
230       } else {
231         // compute the common file hierarchy between base and currentHierarchy
232         int len = Math.min(base.length, currentHierarchy.length);
233         List newHierarchy = new ArrayList();
234         for (int i = 0; i < len; i++) {
235           File a = base[i];
236           File b = currentHierarchy[i];
237           if (a.equals(b)) {
238             // a directory is shared : great !
239             newHierarchy.add(a);
240           } else {
241             // it's the first directory that is not shared : the common file hierarchy is over
242             if (newHierarchy.size() == 1) {
243               // we're already to the source directory : don't need to search further, we can't get better
244               return sourceDirectory;
245             }
246             base = new File[newHierarchy.size()];
247             newHierarchy.toArray(base);
248             break;
249           }
250         }
251       }
252     }
253     // Yes, this work has lead to a common base different than sourceDirectory
254     return base[base.length - 1];
255   }
256 }