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