View Javadoc
1   package org.codehaus.mojo.jaxb2;
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 org.apache.maven.model.Resource;
23  import org.apache.maven.plugin.AbstractMojo;
24  import org.apache.maven.plugin.MojoExecution;
25  import org.apache.maven.plugin.MojoExecutionException;
26  import org.apache.maven.plugin.MojoFailureException;
27  import org.apache.maven.plugin.logging.Log;
28  import org.apache.maven.plugins.annotations.Component;
29  import org.apache.maven.plugins.annotations.Parameter;
30  import org.apache.maven.project.MavenProject;
31  import org.codehaus.mojo.jaxb2.shared.FileSystemUtilities;
32  import org.codehaus.mojo.jaxb2.shared.Validate;
33  import org.codehaus.mojo.jaxb2.shared.environment.EnvironmentFacet;
34  import org.codehaus.mojo.jaxb2.shared.filters.Filter;
35  import org.codehaus.mojo.jaxb2.shared.filters.pattern.FileFilterAdapter;
36  import org.codehaus.mojo.jaxb2.shared.filters.pattern.PatternFileFilter;
37  import org.codehaus.mojo.jaxb2.shared.version.DependencyInfo;
38  import org.codehaus.mojo.jaxb2.shared.version.DependsFileParser;
39  import org.sonatype.plexus.build.incremental.BuildContext;
40  
41  import java.io.File;
42  import java.io.FileFilter;
43  import java.io.IOException;
44  import java.net.URL;
45  import java.nio.file.Path;
46  import java.nio.file.Paths;
47  import java.util.ArrayList;
48  import java.util.Arrays;
49  import java.util.Collections;
50  import java.util.List;
51  import java.util.Locale;
52  import java.util.Map;
53  import java.util.SortedMap;
54  import java.util.TreeMap;
55  import java.util.concurrent.atomic.AtomicInteger;
56  import java.util.regex.Pattern;
57  
58  /**
59   * Abstract Mojo which collects common infrastructure, required and needed
60   * by all subclass Mojos in the JAXB2 maven plugin codebase.
61   *
62   * @author <a href="mailto:lj@jguru.se">Lennart J&ouml;relid</a>
63   */
64  public abstract class AbstractJaxbMojo extends AbstractMojo {
65  
66      /**
67       * Standard name of the generated JAXB episode file.
68       */
69      public static final String STANDARD_EPISODE_FILENAME = "sun-jaxb.episode";
70  
71      /**
72       * Standard name of the package-info.java file which may contain
73       * JAXB annotations and Package JavaDoc.
74       */
75      public static final String PACKAGE_INFO_FILENAME = "package-info.java";
76  
77      /**
78       * Platform-independent newline control string.
79       */
80      public static final String NEWLINE = System.getProperty("line.separator");
81  
82      /**
83       * Pattern matching strings containing whitespace (or consisting only of whitespace).
84       */
85      public static final Pattern CONTAINS_WHITESPACE = Pattern.compile("(\\S*\\s+\\S*)+", Pattern.UNICODE_CASE);
86  
87      /**
88       * <p>Standard excludes Filters for all Java generator Mojos.
89       * The List is unmodifiable, and contains Filters on the following form:</p>
90       * <pre>
91       *     <code>
92       *         // The standard exclude filters contain simple, exclude pattern filters.
93       *         final List&lt;Filter&lt;File&gt;&gt; tmp = new ArrayList&lt;Filter&lt;File&gt;&gt;();
94       *         tmp.add(new PatternFileFilter(Arrays.asList({"README.*", "\\.xml", "\\.txt"}), true));
95       *         tmp.add(new FileFilterAdapter(new FileFilter() {
96       *
97       *             &#64;Override
98       *             public boolean accept(final File aFileOrDir) {
99       *
100      *                 // Check sanity
101      *                 if (aFileOrDir == null) {
102      *                     return false;
103      *                 }
104      *
105      *                 final String name = aFileOrDir.getName();
106      *
107      *                 // Ignore hidden files and CVS directories
108      *                 return name.startsWith(".")
109      *                         || (aFileOrDir.isDirectory() &amp;&amp; name.equals("CVS"));
110      *
111      *             }
112      *         }));
113      *     </code>
114      * </pre>
115      * <p><b>Note</b>! Since the plugin is currently developed in jdk 1.7-compliant code, we cannot
116      * use lambdas within Filters just yet.</p>
117      */
118     public static final List<Filter<File>> STANDARD_EXCLUDE_FILTERS;
119 
120     private static final List<String> RELEVANT_GROUPIDS =
121             Arrays.asList("org.glassfish.jaxb", "jakarta.xml.bind");
122     private static final String OWN_ARTIFACT_ID = "jaxb2-maven-plugin";
123     private static final String SYSTEM_FILE_ENCODING_PROPERTY = "file.encoding";
124     private static final String[] STANDARD_EXCLUDE_SUFFIXES = {"README.*", "\\.xml", "\\.txt"};
125     private static final String[] STANDARD_PRELOADED_CLASSES = {
126             "com.sun.tools.xjc.addon.episode.package-info",
127             "com.sun.tools.xjc.reader.xmlschema.bindinfo.package-info",
128             "com.sun.xml.bind.v2.model.core.package-info",
129             "com.sun.xml.bind.v2.model.runtime.package-info",
130             "com.sun.xml.bind.v2.schemagen.episode.package-info",
131             "com.sun.xml.bind.v2.schemagen.xmlschema.package-info"
132     };
133 
134     static {
135 
136         // The standard exclude filters contain simple, exclude pattern filters.
137         final List<Filter<File>> tmp = new ArrayList<Filter<File>>();
138         tmp.add(new PatternFileFilter(Arrays.asList(STANDARD_EXCLUDE_SUFFIXES), true));
139         tmp.add(new FileFilterAdapter(new FileFilter() {
140             @Override
141             public boolean accept(final File aFileOrDir) {
142 
143                 // Check sanity
144                 if (aFileOrDir == null) {
145                     return false;
146                 }
147 
148                 final String name = aFileOrDir.getName();
149 
150                 // Ignore hidden files and CVS directories
151                 return name.startsWith(".")
152                         || (aFileOrDir.isDirectory() && name.equals("CVS"));
153 
154             }
155         }));
156 
157         // Make STANDARD_EXCLUDE_FILTERS be unmodifiable.
158         STANDARD_EXCLUDE_FILTERS = Collections.unmodifiableList(tmp);
159 
160         // TODO: These are hardcoded. Move to overridable using a system property?
161         // Preload relevant package-info classes to work around MNG-6506.
162         try {
163 
164             final ClassLoader cl = AbstractJaxbMojo.class.getClassLoader();
165 
166             for(String current : STANDARD_PRELOADED_CLASSES) {
167                 cl.loadClass(current);
168             }
169 
170         } catch (ClassNotFoundException ex) {
171             throw new Error(ex);
172         }
173     }
174 
175     /**
176      * The Plexus BuildContext is used to identify files or directories modified since last build,
177      * implying functionality used to define if java generation must be performed again.
178      */
179     @Component
180     private BuildContext buildContext;
181 
182     /**
183      * The injected Maven project.
184      */
185     @Parameter(defaultValue = "${project}", readonly = true)
186     private MavenProject project;
187 
188     /**
189      * Note that the execution parameter will be injected ONLY if this plugin is executed as part
190      * of a maven standard lifecycle - as opposed to directly invoked with a direct invocation.
191      * When firing this mojo directly (i.e. {@code mvn xjc:something} or {@code mvn schemagen:something}), the
192      * {@code execution} object will not be injected.
193      */
194     @Parameter(defaultValue = "${mojoExecution}", readonly = true)
195     private MojoExecution execution;
196 
197     /**
198      * <p>The directory where the staleFile is found.
199      * The staleFile assists in determining if re-generation of JAXB build products is required.</p>
200      * <p>While it is permitted to re-define the staleFileDirectory, it is recommended to keep it
201      * below the <code>${project.build.directory}</code>, to ensure that JAXB code or XSD re-generation
202      * occurs after cleaning the project.</p>
203      *
204      * @since 2.0
205      */
206     @Parameter(defaultValue = "${project.build.directory}/jaxb2", readonly = true, required = true)
207     protected File staleFileDirectory;
208 
209     /**
210      * <p>Defines the encoding used by XJC (for generating Java Source files) and schemagen (for generating XSDs).
211      * The corresponding argument parameter for XJC and SchemaGen is: {@code encoding}.</p>
212      * <p>The algorithm for finding the encoding to use is as follows
213      * (where the first non-null value found is used for encoding):
214      * <ol>
215      * <li>If the configuration property is explicitly given within the plugin's configuration, use that value.</li>
216      * <li>If the Maven property <code>project.build.sourceEncoding</code> is defined, use its value.</li>
217      * <li>Otherwise use the value from the system property <code>file.encoding</code>.</li>
218      * </ol>
219      * </p>
220      *
221      * @see #getEncoding(boolean)
222      * @since 2.0
223      */
224     @Parameter(defaultValue = "${project.build.sourceEncoding}")
225     private String encoding;
226 
227     /**
228      * <p>A Locale definition to create and set the system (default) Locale when the XJB or SchemaGen tools executes.
229      * The Locale will be reset to its default value after the execution of XJC or SchemaGen is complete.</p>
230      * <p>The configuration parameter must be supplied on the form {@code language[,country[,variant]]},
231      * such as {@code sv,SE} or {@code fr}. Refer to
232      * {@code org.codehaus.mojo.jaxb2.shared.environment.locale.LocaleFacet.createFor(String, Log)} for further
233      * information.</p>
234      * <p><strong>Example</strong> (assigns french locale):</p>
235      * <pre>
236      *     <code>
237      *         &lt;configuration&gt;
238      *              &lt;locale&gt;fr&lt;/locale&gt;
239      *         &lt;/configuration&gt;
240      *     </code>
241      * </pre>
242      *
243      * @see org.codehaus.mojo.jaxb2.shared.environment.locale.LocaleFacet#createFor(String, Log)
244      * @see Locale#getAvailableLocales()
245      * @since 2.2
246      */
247     @Parameter(required = false)
248     protected String locale;
249 
250     /**
251      * <p>Defines a set of extra EnvironmentFacet instances which are used to further configure the
252      * ToolExecutionEnvironment used by this plugin to fire XJC or SchemaGen.</p>
253      * <p><em>Example:</em> If you implement the EnvironmentFacet interface in the class
254      * {@code org.acme.MyCoolEnvironmentFacetImplementation}, its {@code setup()} method is called before the
255      * XJC or SchemaGen tools are executed to setup some facet of their Execution environment. Correspondingly, the
256      * {@code restore()} method in your {@code org.acme.MyCoolEnvironmentFacetImplementation} class is invoked after
257      * the XJC or SchemaGen execution terminates.</p>
258      * <pre>
259      *     <code>
260      *         &lt;configuration&gt;
261      *         ...
262      *              &lt;extraFacets&gt;
263      *                  &lt;extraFacet implementation="org.acme.MyCoolEnvironmentFacetImplementation" /&gt;
264      *              &lt;/extraFacets&gt;
265      *         ...
266      *         &lt;/configuration&gt;
267      *     </code>
268      * </pre>
269      *
270      * @see EnvironmentFacet
271      * @see org.codehaus.mojo.jaxb2.shared.environment.ToolExecutionEnvironment#add(EnvironmentFacet)
272      * @since 2.2
273      */
274     @Parameter(required = false)
275     protected List<EnvironmentFacet> extraFacets;
276 
277     /**
278      * Adds the supplied Resource to the project using the appropriate scope (i.e. resource or testResource)
279      * depending on the exact implementation of this AbstractJaxbMojo.
280      *
281      * @param resource The resource to add.
282      */
283     protected abstract void addResource(final Resource resource);
284 
285     /**
286      * The Plexus BuildContext is used to identify files or directories modified since last build,
287      * implying functionality used to define if java generation must be performed again.
288      *
289      * @return the active Plexus BuildContext.
290      */
291     protected final BuildContext getBuildContext() {
292         return getInjectedObject(buildContext, "buildContext");
293     }
294 
295     /**
296      * @return The active MavenProject.
297      */
298     protected final MavenProject getProject() {
299         return getInjectedObject(project, "project");
300     }
301 
302     /**
303      * @return The active MojoExecution.
304      */
305     public MojoExecution getExecution() {
306         return getInjectedObject(execution, "execution");
307     }
308 
309     /**
310      * {@inheritDoc}
311      */
312     @Override
313     public final void execute() throws MojoExecutionException, MojoFailureException {
314 
315         // 0) Get the log and its relevant level
316         final Log log = getLog();
317         final boolean isDebugEnabled = log.isDebugEnabled();
318         final boolean isInfoEnabled = log.isInfoEnabled();
319 
320         // 1) Should we skip execution?
321         if (shouldExecutionBeSkipped()) {
322 
323             if (isDebugEnabled) {
324                 log.debug("Skipping execution, as instructed.");
325             }
326             return;
327         }
328 
329         // 2) Printout relevant version information.
330         if (isDebugEnabled) {
331             logPluginAndJaxbDependencyInfo();
332         }
333 
334         // 3) Are generated files stale?
335         if (isReGenerationRequired()) {
336 
337             if (performExecution()) {
338 
339                 // As instructed by the performExecution() method, update
340                 // the timestamp of the stale File.
341                 updateStaleFileTimestamp();
342 
343                 // Hack to support M2E
344                 buildContext.refresh(getOutputDirectory());
345 
346             } else if (isInfoEnabled) {
347                 log.info("Not updating staleFile timestamp as instructed.");
348             }
349         } else if (isInfoEnabled) {
350             log.info("No changes detected in schema or binding files - skipping JAXB generation.");
351         }
352 
353         // 4) If the output directories exist, add them to the MavenProject's source directories
354         if (getOutputDirectory().exists() && getOutputDirectory().isDirectory()) {
355 
356             final String canonicalPathToOutputDirectory = FileSystemUtilities.getCanonicalPath(getOutputDirectory());
357 
358             if (log.isDebugEnabled()) {
359                 log.debug("Adding existing JAXB outputDirectory [" + canonicalPathToOutputDirectory
360                         + "] to Maven's sources.");
361             }
362 
363             // Add the output Directory.
364             getProject().addCompileSourceRoot(canonicalPathToOutputDirectory);
365         }
366     }
367 
368     /**
369      * Implement this method to check if this AbstractJaxbMojo should skip executing altogether.
370      *
371      * @return {@code true} to indicate that this AbstractJaxbMojo should bail out of its execute method.
372      */
373     protected abstract boolean shouldExecutionBeSkipped();
374 
375     /**
376      * @return {@code true} to indicate that this AbstractJaxbMojo should be run since its generated files were
377      * either stale or not present, and {@code false} otherwise.
378      */
379     protected abstract boolean isReGenerationRequired();
380 
381     /**
382      * <p>Implement this method to perform this Mojo's execution.
383      * This method will only be called if {@code !shouldExecutionBeSkipped() && isReGenerationRequired()}.</p>
384      *
385      * @return {@code true} if the timestamp of the stale file should be updated.
386      * @throws MojoExecutionException if an unexpected problem occurs.
387      *                                Throwing this exception causes a "BUILD ERROR" message to be displayed.
388      * @throws MojoFailureException   if an expected problem (such as a compilation failure) occurs.
389      *                                Throwing this exception causes a "BUILD FAILURE" message to be displayed.
390      */
391     protected abstract boolean performExecution() throws MojoExecutionException, MojoFailureException;
392 
393     /**
394      * Override this method to acquire a List holding all URLs to the sources which this
395      * AbstractJaxbMojo should use to produce its output (XSDs files for AbstractXsdGeneratorMojos and
396      * Java Source Code for AbstractJavaGeneratorMojos).
397      *
398      * @return A non-null List holding URLs to sources used by this AbstractJaxbMojo to produce its output.
399      */
400     protected abstract List<URL> getSources();
401 
402     /**
403      * Retrieves the directory where the generated files should be written to.
404      *
405      * @return the directory where the generated files should be written to.
406      */
407     protected abstract File getOutputDirectory();
408 
409     /**
410      * Retrieves the configured List of paths from which this AbstractJaxbMojo and its internal toolset
411      * (XJC or SchemaGen) should read bytecode classes.
412      *
413      * @return the configured List of paths from which this AbstractJaxbMojo and its internal toolset (XJC or
414      * SchemaGen) should read classes.
415      * @throws org.apache.maven.plugin.MojoExecutionException if the classpath could not be retrieved.
416      */
417     protected abstract List<String> getClasspath() throws MojoExecutionException;
418 
419     /**
420      * Convenience method to invoke when some plugin configuration is incorrect.
421      * Will output the problem as a warning with some degree of log formatting.
422      *
423      * @param propertyName The name of the problematic property.
424      * @param description  The problem description.
425      */
426     @SuppressWarnings("all")
427     protected void warnAboutIncorrectPluginConfiguration(final String propertyName, final String description) {
428 
429         final StringBuilder builder = new StringBuilder();
430         builder.append("\n+=================== [Incorrect Plugin Configuration Detected]\n");
431         builder.append("|\n");
432         builder.append("| Property : " + propertyName + "\n");
433         builder.append("| Problem  : " + description + "\n");
434         builder.append("|\n");
435         builder.append("+=================== [End Incorrect Plugin Configuration Detected]\n\n");
436         getLog().warn(builder.toString().replace("\n", NEWLINE));
437     }
438 
439     /**
440      * @param arguments The final arguments to be passed to a JAXB tool (XJC or SchemaGen).
441      * @param toolName  The name of the tool.
442      * @return the arguments, untouched.
443      */
444     protected final String[] logAndReturnToolArguments(final String[] arguments, final String toolName) {
445 
446         // Check sanity
447         Validate.notNull(arguments, "arguments");
448 
449         if (getLog().isDebugEnabled()) {
450 
451             final StringBuilder argBuilder = new StringBuilder();
452             argBuilder.append("\n+=================== [" + arguments.length + " " + toolName + " Arguments]\n");
453             argBuilder.append("|\n");
454             for (int i = 0; i < arguments.length; i++) {
455                 argBuilder.append("| [").append(i).append("]: ").append(arguments[i]).append("\n");
456             }
457             argBuilder.append("|\n");
458             argBuilder.append("+=================== [End " + arguments.length + " " + toolName + " Arguments]\n\n");
459             getLog().debug(argBuilder.toString().replace("\n", NEWLINE));
460         }
461 
462         // All done.
463         return arguments;
464     }
465 
466     /**
467      * Retrieves the last name part of the stale file.
468      * The full name of the stale file will be generated by pre-pending {@code "." + getExecution().getExecutionId()}
469      * before this staleFileName.
470      *
471      * @return The name of the stale file used by this AbstractJavaGeneratorMojo to detect staleness amongst its
472      * generated files.
473      */
474     protected abstract String getStaleFileName();
475 
476     /**
477      * Acquires the staleFile for this execution
478      *
479      * @return the staleFile (used to define where) for this execution
480      */
481     protected final File getStaleFile() {
482         final String staleFileName = "."
483                 + (getExecution() == null ? "nonExecutionJaxb" : getExecution().getExecutionId())
484                 + "-" + getStaleFileName();
485         return new File(staleFileDirectory, staleFileName);
486     }
487 
488     /**
489      * <p>The algorithm for finding the encoding to use is as follows (where the first non-null value found
490      * is used for encoding):</p>
491      * <ol>
492      * <li>If the configuration property is explicitly given within the plugin's configuration, use that value.</li>
493      * <li>If the Maven property <code>project.build.sourceEncoding</code> is defined, use its value.</li>
494      * <li>Otherwise use the value from the system property <code>file.encoding</code>.</li>
495      * </ol>
496      *
497      * @param warnIfPlatformEncoding Defines if a warning should be logged if encoding is not configured but
498      *                               the platform encoding (system property {@code file.encoding}) is used
499      * @return The encoding to be used by this AbstractJaxbMojo and its tools.
500      * @see #encoding
501      */
502     protected final String getEncoding(final boolean warnIfPlatformEncoding) {
503 
504         // Harvest information
505         final boolean configuredEncoding = encoding != null;
506         final String fileEncoding = System.getProperty(SYSTEM_FILE_ENCODING_PROPERTY);
507         final String effectiveEncoding = configuredEncoding ? encoding : fileEncoding;
508 
509         // Should we warn if using platform encoding (i.e. platform dependent)?
510         if (!configuredEncoding && warnIfPlatformEncoding) {
511             getLog().warn("Using platform encoding [" + effectiveEncoding + "], i.e. build is platform dependent!");
512         } else if (getLog().isDebugEnabled()) {
513             getLog().debug("Using " + (configuredEncoding ? "explicitly configured" : "system property")
514                     + " encoding [" + effectiveEncoding + "]");
515         }
516 
517         // All Done.
518         return effectiveEncoding;
519     }
520 
521     /**
522      * Retrieves a File to the JAXB Episode (which is normally written during the XJC process).
523      * Moreover, ensures that the parent directory of that File is created, to enable writing the File.
524      *
525      * @param episodeFileName {@code null} to indicate that the standard episode file name ("sun-jaxb.episode")
526      *                        should be used, and otherwise a non-empty name which should be used
527      *                        as the episode file name.
528      * @return A non-null File where the JAXB episode file should be written.
529      * @throws MojoExecutionException if the parent directory of the episode file could not be created.
530      */
531     protected File getEpisodeFile(final String episodeFileName) throws MojoExecutionException {
532 
533         // Get the execution ID
534         final String executionID = getExecution() != null && getExecution().getExecutionId() != null
535                 ? getExecution().getExecutionId()
536                 : null;
537 
538         final String effectiveEpisodeFileName = episodeFileName == null
539                 ? (executionID == null ? STANDARD_EPISODE_FILENAME : "episode_" + executionID)
540                 : episodeFileName;
541         if (effectiveEpisodeFileName.isEmpty()) {
542             throw new MojoExecutionException("Cannot handle null or empty JAXB Episode filename. "
543                     + "Check 'episodeFileName' configuration property.");
544         }
545 
546         // Find or create the episode directory.
547         final Path episodePath;
548         final File generatedJaxbEpisodeDirectory;
549         try {
550             final Path path = Paths.get(getOutputDirectory().getAbsolutePath(), "META-INF", "JAXB");
551             episodePath = java.nio.file.Files.createDirectories(path);
552             generatedJaxbEpisodeDirectory = episodePath.toFile();
553 
554             if (getLog().isInfoEnabled()) {
555                 getLog().info("Created EpisodePath [" + episodePath.toString() + "]: " +
556                         (generatedJaxbEpisodeDirectory.exists() && generatedJaxbEpisodeDirectory.isDirectory()));
557             }
558 
559         } catch (IOException e) {
560             throw new MojoExecutionException("Could not create output directory.", e);
561         }
562 
563         if (!generatedJaxbEpisodeDirectory.exists() || !generatedJaxbEpisodeDirectory.isDirectory()) {
564             throw new MojoExecutionException("Could not create directory [" + episodePath.toString() + "]");
565         }
566 
567         // Is there already an episode file here?
568         File episodeFile = new File(generatedJaxbEpisodeDirectory, effectiveEpisodeFileName + ".xjb");
569         final AtomicInteger index = new AtomicInteger(1);
570         while (episodeFile.exists()) {
571             episodeFile = new File(generatedJaxbEpisodeDirectory,
572                     effectiveEpisodeFileName + "_" + index.getAndIncrement() + ".xjb");
573         }
574 
575         // Add the (generated) outputDirectory to the Resources.
576         final Resource outputDirectoryResource = new Resource();
577         outputDirectoryResource.setDirectory(getOutputDirectory().getAbsolutePath());
578         outputDirectoryResource.setIncludes(Collections.singletonList("**/" + episodeFile.getName()));
579         this.addResource(outputDirectoryResource);
580 
581         // All Done.
582         return episodeFile;
583     }
584 
585     //
586     // Private helpers
587     //
588 
589     private void logPluginAndJaxbDependencyInfo() {
590 
591         if (getLog().isDebugEnabled()) {
592             final StringBuilder builder = new StringBuilder();
593             builder.append("\n+=================== [Brief Plugin Build Dependency Information]\n");
594             builder.append("|\n");
595             builder.append("| Note: These dependencies pertain to what was used to build *the plugin*.\n");
596             builder.append("|       Check project dependencies to see the ones used in *your build*.\n");
597             builder.append("|\n");
598 
599             // Find the dependency and version information within the dependencies.properties file.
600             final SortedMap<String, String> versionMap = DependsFileParser.getVersionMap(OWN_ARTIFACT_ID);
601 
602             builder.append("|\n");
603             builder.append("| Plugin's own information\n");
604             builder.append("|     GroupId    : " + versionMap.get(DependsFileParser.OWN_GROUPID_KEY) + "\n");
605             builder.append("|     ArtifactID : " + versionMap.get(DependsFileParser.OWN_ARTIFACTID_KEY) + "\n");
606             builder.append("|     Version    : " + versionMap.get(DependsFileParser.OWN_VERSION_KEY) + "\n");
607             builder.append("|     Buildtime  : " + versionMap.get(DependsFileParser.BUILDTIME_KEY) + "\n");
608             builder.append("|\n");
609             builder.append("| Plugin's JAXB-related dependencies\n");
610             builder.append("|\n");
611 
612             final SortedMap<String, DependencyInfo> diMap = DependsFileParser.createDependencyInfoMap(versionMap);
613 
614             int dependencyIndex = 0;
615             for (Map.Entry<String, DependencyInfo> current : diMap.entrySet()) {
616 
617                 final String key = current.getKey().trim();
618                 for (String currentRelevantGroupId : RELEVANT_GROUPIDS) {
619                     if (key.startsWith(currentRelevantGroupId)) {
620 
621                         final DependencyInfo di = current.getValue();
622                         builder.append("|   " + (++dependencyIndex) + ") [" + di.getArtifactId() + "]\n");
623                         builder.append("|     GroupId    : " + di.getGroupId() + "\n");
624                         builder.append("|     ArtifactID : " + di.getArtifactId() + "\n");
625                         builder.append("|     Version    : " + di.getVersion() + "\n");
626                         builder.append("|     Scope      : " + di.getScope() + "\n");
627                         builder.append("|     Type       : " + di.getType() + "\n");
628                         builder.append("|\n");
629                     }
630                 }
631             }
632 
633             builder.append("+=================== [End Brief Plugin Build Dependency Information]\n\n");
634             getLog().debug(builder.toString().replace("\n", NEWLINE));
635         }
636     }
637 
638     private <T> T getInjectedObject(final T objectOrNull, final String objectName) {
639 
640         if (objectOrNull == null) {
641             getLog().error(
642                     "Found null '" + objectName + "', implying that Maven @Component injection was not done properly.");
643         }
644 
645         return objectOrNull;
646     }
647 
648     private void updateStaleFileTimestamp() throws MojoExecutionException {
649 
650         final File staleFile = getStaleFile();
651         if (!staleFile.exists()) {
652 
653             // Ensure that the staleFileDirectory exists
654             FileSystemUtilities.createDirectory(staleFile.getParentFile(), false);
655 
656             try {
657                 staleFile.createNewFile();
658 
659                 if (getLog().isDebugEnabled()) {
660                     getLog().debug("Created staleFile [" + FileSystemUtilities.getCanonicalPath(staleFile) + "]");
661                 }
662             } catch (IOException e) {
663                 throw new MojoExecutionException("Could not create staleFile.", e);
664             }
665 
666         } else {
667             if (!staleFile.setLastModified(System.currentTimeMillis())) {
668                 getLog().warn("Failed updating modification time of staleFile ["
669                         + FileSystemUtilities.getCanonicalPath(staleFile) + "]");
670             }
671         }
672     }
673 
674     /**
675      * Prints out the system properties to the Maven Log at Debug level.
676      */
677     protected void logSystemPropertiesAndBasedir() {
678         if (getLog().isDebugEnabled()) {
679 
680             final StringBuilder builder = new StringBuilder();
681 
682             builder.append("\n+=================== [System properties]\n");
683             builder.append("|\n");
684 
685             // Sort the system properties
686             final SortedMap<String, Object> props = new TreeMap<String, Object>();
687             props.put("basedir", FileSystemUtilities.getCanonicalPath(getProject().getBasedir()));
688 
689             for (Map.Entry<Object, Object> current : System.getProperties().entrySet()) {
690                 props.put("" + current.getKey(), current.getValue());
691             }
692             for (Map.Entry<String, Object> current : props.entrySet()) {
693                 builder.append("| [" + current.getKey() + "]: " + current.getValue() + "\n");
694             }
695 
696             builder.append("|\n");
697             builder.append("+=================== [End System properties]\n");
698 
699             // All done.
700             getLog().debug(builder.toString().replace("\n", NEWLINE));
701         }
702     }
703 }