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.doxia.siterenderer;
20  
21  import java.io.File;
22  import java.util.Collection;
23  import java.util.Collections;
24  import java.util.HashMap;
25  import java.util.Map;
26  import java.util.Objects;
27  
28  import org.codehaus.plexus.util.PathTool;
29  
30  /**
31   * The rendering context of a document.
32   * If not rendered from a Doxia markup source, parserId and extension will be null.
33   *
34   * @author <a href="mailto:jason@maven.org">Jason van Zyl</a>
35   * @since 1.5 (was since 1.1 in o.a.m.d.sink.render)
36   */
37  public class DocumentRenderingContext {
38  
39      /** absolute path to the source base directory (not null, pseudo value when not a Doxia source), this is parser format specific. Must start with {@link #siteRootDirectory}. */
40      private final File basedir;
41  
42      /** {@link #basedir} relative path to the document's source including {@link #extension}. May be {@code null} if not rendered from a Doxia source */
43      private final String inputPath;
44  
45      /** same as {@link #inputPath} but with extension replaced with {@code .html}, this is parser format specific */
46      private final String outputPath;
47  
48      /** the Doxia module parser id associated to this document, may be null if document not rendered from a Doxia source. */
49      private final String parserId;
50  
51      /** the source document filename extension, may be null if document not rendered from a Doxia source. */
52      private final String extension;
53  
54      private Map<String, String> attributes;
55  
56      /**
57       * The absolute paths of directories which may contain the original editable source.
58       * If empty document is not editable.
59       */
60      private final Collection<File> sourceDirectories;
61  
62      /** The project's build directory, may be {@code null} rendered from a Doxia source) */
63      private final File rootDirectory;
64  
65      /** The site's root directory, must be below {@link #rootDirectory}, may be {@code null} if not rendered from a Doxia source */
66      private final File siteRootDirectory;
67  
68      /** optional descriptive text of the plugin which generated the output (usually Maven coordinates). Only set when document is not based on a Doxia source. */
69      private final String generator;
70  
71      static File stripSuffixFromPath(File file, String suffix) {
72          File relevantFile = file;
73          if (suffix == null || suffix.isEmpty()) {
74              return relevantFile;
75          }
76          File currentSuffixPart = new File(suffix);
77          // compare elements from end, suffix should be a suffix of file
78          do {
79              if (currentSuffixPart.getName().equals(relevantFile.getName())) {
80                  relevantFile = relevantFile.getParentFile();
81                  if (relevantFile == null) {
82                      throw new IllegalArgumentException("Suffix " + suffix + " has more elements than file " + file);
83                  }
84              } else {
85                  throw new IllegalArgumentException("Suffix " + suffix + " is not a suffix of file " + file);
86              }
87          } while ((currentSuffixPart = currentSuffixPart.getParentFile()) != null);
88          return relevantFile;
89      }
90  
91      /**
92       * <p>
93       * Constructor for rendering context when document is not rendered from a Doxia markup source.
94       * </p>
95       *
96       * @param basedir the pseudo-source base directory.
97       * @param document the pseudo-source document path: will be used to compute output path (same path with extension
98       *            replaced with <code>.html</code>).
99       * @param generator the generator (in general a reporting goal: <code>groupId:artifactId:version:goal</code>)
100      * @since 1.8
101      */
102     public DocumentRenderingContext(File basedir, String document, String generator) {
103         this(basedir, document, null, null, null, null, Collections.emptySet(), generator);
104     }
105 
106     /**
107      *
108      * @param basedir
109      * @param basedirRelativePath
110      * @param document
111      * @param parserId
112      * @param extension
113      * @param editable
114      * @deprecated Use {@link #DocumentRenderingContext(File, String, String, String, File, File, Collection, String)} instead.
115      */
116     @Deprecated
117     public DocumentRenderingContext(
118             File basedir,
119             String basedirRelativePath,
120             String document,
121             String parserId,
122             String extension,
123             boolean editable) {
124         this(basedir, basedirRelativePath, document, parserId, extension, editable, null);
125     }
126 
127     /**
128      * <p>
129      * Constructor for rendering context when document is a Doxia markup source.
130      * </p>
131      * Same as {@link #DocumentRenderingContext(File, String, String, String, File, File, Collection, String)} with {@code generator} set to {@code null}.
132      *
133      * @param basedir the source base directory (not null, pseudo value when not a Doxia source).
134      * @param document the source document path.
135      * @param parserId the Doxia module parser id associated with this document, may be null if document not rendered from
136      *            a Doxia source.
137      * @param extension the source document filename extension, may be null if document not rendered from
138      *            a Doxia source.
139      * @param rootDirectory the absolute project's root directory (not null), usually {@code project.basedir}, must be an ancestor of {@code siteRootDirectory}
140      * @param siteRootDirectory the absolute site's root directory (not null), must start with {@code rootDirectory}, often ends with {@code /src/site}
141      * @param sourceDirectories the absolute paths of directories which may contain the original editable source.
142      * @since 2.1
143      */
144     public DocumentRenderingContext(
145             File basedir,
146             String document,
147             String parserId,
148             String extension,
149             File rootDirectory,
150             File siteRootDirectory,
151             Collection<File> sourceDirectories) {
152         this(basedir, document, parserId, extension, rootDirectory, siteRootDirectory, sourceDirectories, null);
153     }
154 
155     /**
156      * <p>
157      * Constructor for document rendering context.
158      * </p>
159      *
160      * @param basedir the absolute source base directory (not null, pseudo value when not a Doxia source).
161      * @param basedirRelativePath the relative path of {@code #basedir} from project root (null if not Doxia source)
162      * @param document the source document path.
163      * @param parserId the Doxia module parser id associated with this document, may be null if document not rendered from
164      *            a Doxia source.
165      * @param extension the source document filename extension, may be null if document not rendered from
166      *            a Doxia source.
167      * @param editable {@code true} if the document is editable as source, i.e. not generated, {@code false} otherwise.
168      * @param generator the generator of this document (in general a reporting goal: <code>groupId:artifactId:version:goal</code>), not set when document is based on a Doxia source.
169      * @since 1.8
170      * @deprecated Use {@link #DocumentRenderingContext(File, String, String, String, File, File, Collection, String)} instead.
171      */
172     @Deprecated
173     public DocumentRenderingContext(
174             File basedir,
175             String basedirRelativePath,
176             String document,
177             String parserId,
178             String extension,
179             boolean editable,
180             String generator) {
181         this(
182                 basedir,
183                 document,
184                 parserId,
185                 extension,
186                 stripSuffixFromPath(basedir, basedirRelativePath),
187                 // assume that site root is the parent of basedir (i.e. module specific source directory is directly
188                 // below site root)
189                 basedir.getParentFile(),
190                 editable ? Collections.singleton(basedir) : Collections.emptySet(),
191                 generator);
192     }
193 
194     /**
195      * <p>
196      * Constructor for document rendering context.
197      * </p>
198      *
199      * @param basedir the source base directory (not null, pseudo value when not a Doxia source).
200      * @param document the source document path.
201      * @param parserId the Doxia module parser id associated with this document, may be null if document not rendered from
202      *            a Doxia source.
203      * @param extension the source document filename extension, may be null if document not rendered from
204      *            a Doxia source.
205      * @param rootDirectory the absolute project's root directory (not null), usually {@code project.basedir}, must be an ancestor of {@code siteRootDirectory}
206      * @param siteRootDirectory the absolute site's root directory (not null), must start with {@code rootDirectory}, often ends with {@code /src/site}
207      * @param sourceDirectories the absolute paths of directories which may contain the original editable source.
208      * @param generator the generator (in general a reporting goal: <code>groupId:artifactId:version:goal</code>)
209      * @since 2.1
210      */
211     @SuppressWarnings("checkstyle:parameternumber")
212     public DocumentRenderingContext(
213             File basedir,
214             String document,
215             String parserId,
216             String extension,
217             File rootDirectory,
218             File siteRootDirectory,
219             Collection<File> sourceDirectories,
220             String generator) {
221         this.basedir = basedir;
222         this.parserId = parserId;
223         this.extension = extension;
224         this.generator = generator;
225         this.attributes = new HashMap<>();
226 
227         document = document.replace('\\', '/');
228         this.inputPath = document;
229 
230         if (rootDirectory == null) {
231             if (siteRootDirectory != null) {
232                 throw new IllegalArgumentException("Root directory must not be null when site root directory is set");
233             }
234         } else {
235             Objects.requireNonNull(
236                     siteRootDirectory, "Site root directory must not be null when root directory is set");
237             if (!siteRootDirectory.getPath().startsWith(rootDirectory.getPath())) {
238                 throw new IllegalArgumentException("Site root directory " + siteRootDirectory
239                         + " must start with root directory " + rootDirectory);
240             }
241         }
242         this.rootDirectory = rootDirectory;
243         this.siteRootDirectory = siteRootDirectory;
244         if (extension != null && !extension.isEmpty()) {
245             // document comes from a Doxia source: see DoxiaDocumentRenderer
246             this.sourceDirectories = sourceDirectories;
247 
248             // here we know the parserId and extension, we can play with this to get output name from document:
249             // - index.xml -> index.html
250             // - index.xml.vm -> index.html
251             // - download.apt.vm --> download.html
252             if (DefaultSiteRenderer.endsWithIgnoreCase(document, ".vm")) {
253                 document = document.substring(0, document.length() - 3);
254             }
255             String filePathWithoutExt = document.substring(0, document.length() - extension.length() - 1);
256             this.outputPath = filePathWithoutExt + ".html";
257         } else {
258             // document does not come from a Doxia source but direct Sink API, so no file extension to strip
259             this.sourceDirectories = Collections.emptySet();
260             this.outputPath = document + ".html";
261         }
262     }
263 
264     /**
265      * <p>Getter for the field <code>basedir</code>.</p>
266      *
267      * @return a {@link java.io.File} object.
268      */
269     public File getBasedir() {
270         return basedir;
271     }
272 
273     /**
274      * <p>Getter for the field <code>inputPath</code>.</p>
275      *
276      * @return a {@link java.lang.String} object.
277      */
278     public String getInputPath() {
279         return inputPath;
280     }
281 
282     /**
283      * @deprecated Method name does not properly reflect its purpose. Use {@link #getInputPath()} instead.
284      */
285     @Deprecated
286     public String getInputName() {
287         return getInputPath();
288     }
289 
290     /**
291      * Get html output path, relative to site root.
292      *
293      * @return html output path
294      * @see PathTool#getRelativePath(String)
295      */
296     public String getOutputPath() {
297         return outputPath;
298     }
299 
300     /**
301      * @deprecated Method name does not properly reflect its purpose. Use {@link #getOutputPath()} instead.
302      */
303     @Deprecated
304     public String getOutputName() {
305         return getOutputPath();
306     }
307 
308     /**
309      * Get the parserId when document comes from a Doxia source.
310      *
311      * @return parser id, or <code>null</code> if not froma DOxia source.
312      */
313     public String getParserId() {
314         return parserId;
315     }
316 
317     /**
318      * Get the relative path of the parent directory of this document to site root.
319      *
320      * @return the relative path to site root
321      */
322     public String getRelativePath() {
323         return PathTool.getRelativePath(basedir.getPath(), new File(basedir, inputPath).getPath())
324                 .replace('\\', '/');
325     }
326 
327     /**
328      * <p>setAttribute.</p>
329      *
330      * @param key a {@link java.lang.String} object.
331      * @param value a {@link java.lang.String} object.
332      */
333     public void setAttribute(String key, String value) {
334         attributes.put(key, value);
335     }
336 
337     /**
338      * <p>getAttribute.</p>
339      *
340      * @param key a {@link java.lang.String} object.
341      * @return a {@link java.lang.String} object.
342      */
343     public String getAttribute(String key) {
344         return attributes.get(key);
345     }
346 
347     /**
348      * Get the source document filename extension (when a Doxia source)
349      *
350      * @return the source document filename extension when a Doxia source, or <code>null</code> if not a Doxia source
351      */
352     public String getExtension() {
353         return extension;
354     }
355 
356     /**
357      * Is the source document editable?
358      *
359      * @return <code>true</code> if comes from an editable Doxia source (not generated one).
360      * @since 1.8
361      */
362     public boolean isEditable() {
363         return getDoxiaSourcePath() != null;
364     }
365 
366     /**
367      * Is the document rendered from a Doxia source?
368      *
369      * @return <code>true</code> if comes from a Doxia source.
370      * @since 1.8
371      */
372     public boolean isDoxiaSource() {
373         return extension != null && !extension.isEmpty();
374     }
375 
376     /**
377      * What is the generator (if any)?
378      *
379      * @return <code>null</code> if no known generator
380      * @since 1.8
381      */
382     public String getGenerator() {
383         return generator;
384     }
385 
386     /**
387      * Get the project root relative path of basedir (when a Doxia source). For example {@code src/site/markdown}.
388      *
389      * @return the relative path of basedir when a Doxia source, or <code>null</code> if not a Doxia source
390      * @since 1.8
391      */
392     public String getBasedirRelativePath() {
393         if (!isDoxiaSource()) {
394             return null;
395         }
396         return PathTool.getRelativeFilePath(rootDirectory.getPath(), basedir.getPath());
397     }
398 
399     /**
400      * Get the site root relative path of basedir (when a Doxia source). For example {@code markdown}.
401      *
402      * @return the relative path of basedir when a Doxia source, or <code>null</code> if not a Doxia source
403      */
404     private String getBasedirRelativePathAgainstSiteRoot() {
405         if (!isDoxiaSource()) {
406             return null;
407         }
408         return PathTool.getRelativeFilePath(siteRootDirectory.getPath(), basedir.getPath());
409     }
410 
411     /**
412      * Get the relative path to Doxia source from build root. The file separators in the returned path are {@code /} regardless of the platform..
413      *
414      * @return the relative path to Doxia source from build root, or <code>null</code> if not a Doxia source
415      * @since 1.8
416      */
417     public String getDoxiaSourcePath() {
418         if (!isDoxiaSource()) {
419             return null;
420         } else {
421             for (File sourceDirectory : sourceDirectories) {
422                 File sourceFile = new File(sourceDirectory, getBasedirRelativePathAgainstSiteRoot() + '/' + inputPath);
423                 if (sourceFile.exists()) {
424                     return PathTool.getRelativeFilePath(rootDirectory.getPath(), sourceFile.getPath())
425                             .replace('\\', '/');
426                 }
427             }
428         }
429         return null;
430     }
431 
432     /**
433      * Get absolute url of the Doxia source calculate from given base url.
434      * Used from Skins to render an edit button.
435      *
436      * @param base the base url to use
437      * @return the resulting url
438      * @since 1.8
439      */
440     public String getDoxiaSourcePath(String base) {
441         return PathTool.calculateLink(getDoxiaSourcePath(), base);
442     }
443 }