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 }