View Javadoc

1   package org.apache.maven.cli;
2   
3   /* ====================================================================
4    *   Licensed to the Apache Software Foundation (ASF) under one or more
5    *   contributor license agreements.  See the NOTICE file distributed with
6    *   this work for additional information regarding copyright ownership.
7    *   The ASF licenses this file to You under the Apache License, Version 2.0
8    *   (the "License"); you may not use this file except in compliance with
9    *   the License.  You may obtain a copy of the License at
10   *
11   *       http://www.apache.org/licenses/LICENSE-2.0
12   *
13   *   Unless required by applicable law or agreed to in writing, software
14   *   distributed under the License is distributed on an "AS IS" BASIS,
15   *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   *   See the License for the specific language governing permissions and
17   *   limitations under the License.
18   * ====================================================================
19   */
20  
21  import org.apache.commons.cli.CommandLine;
22  import org.apache.commons.cli.ParseException;
23  import org.apache.commons.jelly.JellyException;
24  import org.apache.commons.jelly.XMLOutput;
25  import org.apache.commons.logging.Log;
26  import org.apache.commons.logging.LogFactory;
27  import org.apache.log4j.Level;
28  import org.apache.log4j.Logger;
29  import org.apache.log4j.PropertyConfigurator;
30  import org.apache.maven.MavenConstants;
31  import org.apache.maven.MavenException;
32  import org.apache.maven.MavenSession;
33  import org.apache.maven.MavenUtils;
34  import org.apache.maven.UnknownGoalException;
35  import org.apache.maven.jelly.MavenJellyContext;
36  import org.apache.maven.project.Project;
37  import org.apache.maven.verifier.ChecksumVerificationException;
38  import org.apache.maven.verifier.RepoConfigException;
39  import org.apache.maven.verifier.UnsatisfiedDependencyException;
40  import org.apache.maven.werkz.NoActionDefinitionException;
41  import org.apache.maven.werkz.NoSuchGoalException;
42  import org.apache.maven.werkz.UnattainableGoalException;
43  
44  import java.io.File;
45  import java.io.FileInputStream;
46  import java.io.FileNotFoundException;
47  import java.io.IOException;
48  import java.io.InputStream;
49  import java.io.OutputStreamWriter;
50  import java.io.Writer;
51  import java.net.MalformedURLException;
52  import java.net.URL;
53  import java.text.BreakIterator;
54  import java.text.DateFormat;
55  import java.util.ArrayList;
56  import java.util.Collections;
57  import java.util.Date;
58  import java.util.HashMap;
59  import java.util.Iterator;
60  import java.util.List;
61  import java.util.Map;
62  import java.util.Properties;
63  import java.util.Set;
64  import java.util.StringTokenizer;
65  
66  /**
67   * The CLI wrapper for controlling MavenSession processes which are encapsulated in the MavenSession bean.
68   * 
69   * @author <a href="mailto:jason@zenplex.com">Jason van Zyl</a>
70   * 
71   * @version $Id: App.java 530196 2007-04-18 23:04:51Z aheritier $
72   * 
73   * @todo Separate the computation of the available goals from the display of the goals. The goal computation logic needs
74   *       to be moved out of this class and be placed in MavenSession.java proper.
75   * @todo All logging needs to be done via commons-logging. No System.err.* and Jelly needs to be taught to take a
76   *       logger. In an attempt to isolate everything in MavenSession.java.
77   */
78  public class App
79  {
80      /** Default file name for an XML-based POM. */
81      public static final String POM_FILE_NAME = "project.xml";
82  
83      /** Default console width - for formatting output. */
84      private static final int CONSOLE_WIDTH = 80;
85  
86      /** Links properties */
87      private static final Properties LINKS_PROPERTIES = new Properties();
88  
89      /** logger for output */
90      private static final Log LOGGER = LogFactory.getLog( App.class );
91  
92      /** Convenience constant for new line character. */
93      private static final String LS = System.getProperty( "line.separator", "\n" );
94  
95      /** Console banner option. */
96      private static final String OPT_CONSOLE_BANNER = "b";
97  
98      /** Debug option. */
99      private static final String OPT_DEBUG = "X";
100 
101     /** Display goals options. */
102     private static final String OPT_DISPLAY_GOALS = "g";
103 
104     /** Display help option. */
105     private static final String OPT_DISPLAY_HELP = "h";
106 
107     /** Display info option. */
108     private static final String OPT_DISPLAY_INFO = "i";
109 
110     /** Display plugin help option. */
111     private static final String OPT_DISPLAY_PLUGIN_HELP = "P";
112 
113     /** Display stack trace option. */
114     private static final String OPT_DISPLAY_STACKTRACE = "e";
115 
116     /** Display project help option. */
117     private static final String OPT_DISPLAY_USAGE = "u";
118 
119     /** Display version option. */
120     private static final String OPT_DISPLAY_VERSION = "v";
121 
122     /** Emacs output option. */
123     private static final String OPT_EMACS_OUTPUT = "E";
124 
125     /** Find POM option. */
126     private static final String OPT_FIND_POM_DESCRIPTOR = "f";
127 
128     /** Quiet option. */
129     private static final String OPT_QUIET = "q";
130 
131     /** Set POM descriptor option. */
132     private static final String OPT_SET_POM_DESCRIPTOR = "p";
133 
134     /** System property option. */
135     private static final String OPT_SET_SYSTEM_PROPERTY = "D";
136 
137     /** Work offline option. */
138     private static final String OPT_WORK_OFFLINE = "o";
139 
140     /** Working dir option. */
141     private static final String OPT_WORKING_DIR = "d";
142 
143     /** return code from command prompt when a bad argument is passed */
144     private static final int RC_BAD_ARG = 10;
145 
146     /** return code for a failure due to Jelly issues */
147     private static final int RC_BAD_JELLY = 80;
148 
149     /** return code for bad repository configuration */
150     private static final int RC_BAD_REPO = 40;
151 
152     /** return code for a goal with no actions */
153     private static final int RC_EMPTY_GOAL = 50;
154 
155     /** return code for a failure due to failed dependency */
156     private static final int RC_FAILED_DEPENDENCY = 100;
157 
158     /** return code for a goal that failed */
159     private static final int RC_GOAL_FAILED = 60;
160 
161     /** return code from command prompt when initialization fails */
162     private static final int RC_INIT_ERROR = 20;
163 
164     /** return code for a goal failed from jelly exception being thrown */
165     private static final int RC_JELLY_FAILED = 70;
166 
167     /** return code from command prompt when a goal isn't found */
168     private static final int RC_NO_GOAL = 30;
169 
170     /** return code for ok processing */
171     private static final int RC_OK = 0;
172 
173     /** return code for a failure due to anything else */
174     private static final int RC_OTHER_FAILURE = 90;
175 
176     /** Default wrap indent. */
177     private static final int WRAP_INDENT = 35;
178 
179     static
180     {
181         URL urlLog4j = App.class.getResource( "/log4j.properties" );
182 
183         if ( urlLog4j == null )
184         {
185             throw new RuntimeException( "Configuration error: can not find the resource log4j.properties" );
186         }
187 
188         PropertyConfigurator.configure( urlLog4j );
189 
190         URL urlLinks = App.class.getResource( "/links.properties" );
191 
192         if ( urlLinks == null )
193         {
194             throw new RuntimeException( "Configuration error: can not find the resource links.properties" );
195         }
196 
197         try
198         {
199             LINKS_PROPERTIES.load( urlLinks.openStream() );
200         }
201         catch ( IOException e )
202         {
203             throw new RuntimeException( "Configuration error: Unable to load the resource links.properties", e );
204         }
205     }
206 
207     /**
208      * Main CLI entry point for MavenSession.
209      * 
210      * @param args
211      *            CLI arguments.
212      */
213     public static void main( String[] args )
214     {
215         Date start = new Date();
216         App app = new App();
217         app.doMain( args, start );
218     }
219 
220     /**
221      * Format a time string.
222      * 
223      * @param ms
224      *            Duration in ms.
225      * @return String The formatted time string.
226      */
227     protected static String formatTime( long ms )
228     {
229         long secs = ms / 1000;
230         long min = secs / 60;
231         secs = secs % 60;
232 
233         if ( min > 0 )
234         {
235             return min + MavenUtils.getMessage( "formatTime.minutes" ) + secs
236                             + MavenUtils.getMessage( "formatTime.seconds" );
237         }
238         else
239         {
240             return secs + MavenUtils.getMessage( "formatTime.seconds" );
241         }
242     }
243 
244     /** CLI Parser */
245     private CommandLine commandLine;
246 
247     /** the session to run builds */
248     private MavenSession mavenSession;
249 
250     /** MavenSession Jelly rootContext. */
251     private MavenJellyContext rootContext;
252 
253     /** Jelly's underlying writer. */
254     private Writer writer;
255 
256     /** Constructor. */
257     public App()
258     {
259     }
260 
261     /**
262      * Perform main operations in a non-static method.
263      * 
264      * @param args
265      *            Arguments passed in from main().
266      * @param fullStart
267      *            Date the mavenSession process was started.
268      */
269     public void doMain( String[] args, Date fullStart )
270     {
271         initializeMain( args );
272 
273         displayHelp();
274         displayVersion();
275 
276         int returnCode = RC_OK;
277 
278         if ( !getCli().hasOption( OPT_CONSOLE_BANNER ) )
279         {
280             printConsoleMavenHeader();
281         }
282 
283         if ( !MavenSession.getRootDescriptorFile().exists() )
284         {
285             LOGGER.warn( MavenUtils.getMessage( "build.no.pom.found" ) );
286             LOGGER.warn( "" );
287         }
288 
289         boolean failed = false;
290         boolean displayStackTrace = getCli().hasOption( OPT_DISPLAY_STACKTRACE ) || getCli().hasOption( OPT_DEBUG );
291 
292         try
293         {
294             mavenSession.initialize();
295 
296             displayInfo();
297             displayProjectHelp();
298             displayPluginHelp();
299 
300             if ( getCli().hasOption( OPT_DISPLAY_GOALS ) )
301             {
302                 displayGoals();
303                 exit( returnCode );
304             }
305             else
306             {
307                 mavenSession.attainGoals( mavenSession.getRootProject(), getCli().getArgList() );
308             }
309         }
310         catch ( UnsatisfiedDependencyException e )
311         {
312             failed = true;
313             displayBuildFailed( e, false, true, displayStackTrace );
314             returnCode = RC_FAILED_DEPENDENCY;
315         }
316         catch ( ChecksumVerificationException e )
317         {
318             failed = true;
319             displayBuildFailed( e, false, true, displayStackTrace );
320             returnCode = RC_FAILED_DEPENDENCY;
321         }
322         catch ( UnknownGoalException e )
323         {
324             failed = true;
325             LOGGER.info( MavenUtils.getMessage( "line" ) );
326             LOGGER.info( MavenUtils.getMessage( "build.unknownGoalException", e.getGoalName() ) );
327             displayBuildFailed( e, false, false, displayStackTrace );
328             returnCode = RC_NO_GOAL;
329         }
330         catch ( NoSuchGoalException e )
331         {
332             failed = true;
333             displayBuildFailed( e, false, true, displayStackTrace );
334             returnCode = RC_NO_GOAL;
335         }
336         catch ( RepoConfigException e )
337         {
338             failed = true;
339             displayBuildFailed( e, false, true, displayStackTrace );
340             returnCode = RC_BAD_REPO;
341         }
342         catch ( NoActionDefinitionException e )
343         {
344             failed = true;
345             LOGGER.info( MavenUtils.getMessage( "line" ) );
346             LOGGER.info( MavenUtils.getMessage( "build.internalError" ) );
347             LOGGER.info( MavenUtils.getMessage( "build.noActionDefinitionException", e.getGoal().getName() ) );
348             displayBuildFailed( e, true, false, displayStackTrace );
349             returnCode = RC_EMPTY_GOAL;
350         }
351         catch ( UnattainableGoalException e )
352         {
353             failed = true;
354             displayBuildFailed( e, false, true, displayStackTrace );
355             if ( e.getCause() instanceof JellyException )
356             {
357                 returnCode = RC_JELLY_FAILED;
358             }
359             else
360             {
361                 returnCode = RC_GOAL_FAILED;
362             }
363         }
364         catch ( JellyException e )
365         {
366             failed = true;
367             displayBuildFailed( e, true, true, displayStackTrace );
368             returnCode = RC_BAD_JELLY;
369         }
370         catch ( MavenException e )
371         {
372             failed = true;
373             displayBuildFailed( e, MavenUtils.MAVEN_UNKNOWN_ERROR.equals( e.getMessage() ), true, displayStackTrace );
374             returnCode = RC_OTHER_FAILURE;
375         }
376         catch ( Throwable t )
377         {
378             failed = true;
379             displayBuildFailed( t, true, true, displayStackTrace );
380             returnCode = RC_OTHER_FAILURE;
381         }
382 
383         if ( !failed )
384         {
385             LOGGER.warn( MavenUtils.getMessage( "line" ) );
386             LOGGER.warn( MavenUtils.getMessage( "build.successful" ) );
387         }
388         LOGGER.warn( MavenUtils.getMessage( "line" ) );
389         Date fullStop = new Date();
390         DateFormat defaultDate = DateFormat.getDateTimeInstance( DateFormat.FULL, DateFormat.LONG );
391         long fullDiff = fullStop.getTime() - fullStart.getTime();
392         LOGGER.warn( MavenUtils.getMessage( "build.total.time", formatTime( fullDiff ) ) );
393         LOGGER.warn( MavenUtils.getMessage( "build.finished.time", defaultDate.format( fullStop ) ) );
394         final long mb = 1024 * 1024;
395         System.gc();
396         Runtime r = Runtime.getRuntime();
397         LOGGER.warn( MavenUtils.getMessage( "build.final.memory", ( ( r.totalMemory() - r.freeMemory() ) / mb ) + "M/"
398                         + ( r.totalMemory() / mb ) + "M" ) );
399         LOGGER.warn( MavenUtils.getMessage( "line" ) );
400         exit( returnCode );
401     }
402 
403     /**
404      * Retrieve the Jelly rootContext.
405      * 
406      * @return The Jelly rootContext.
407      */
408     public MavenJellyContext getRootContext()
409     {
410         return rootContext;
411     }
412 
413     /**
414      * Perform initialization.
415      * 
416      * @param args
417      *            The command-line arguments.
418      * 
419      * @throws ParseException
420      *             If there is an error parsing the command-line.
421      * @throws IOException
422      *             If there is an error while reading the project descriptor.
423      * @throws MalformedURLException
424      *             If any of the the URLs denoting the local or remote repositories is malformed.
425      */
426     public void initialize( String[] args ) throws ParseException, MalformedURLException, IOException
427     {
428         setCli( CLIManager.parse( args ) );
429 
430         initializeSystemProperties();
431         initializeRootContext();
432         initializeMavenSession();
433         customizeLogging();
434     }
435 
436     /**
437      * Setup any system properties that have been specified on the CLI.
438      */
439     public void initializeSystemProperties()
440     {
441         if ( getCli().hasOption( OPT_SET_SYSTEM_PROPERTY ) )
442         {
443             String[] defStrs = getCli().getOptionValues( OPT_SET_SYSTEM_PROPERTY );
444 
445             for ( int i = 0; i < defStrs.length; ++i )
446             {
447                 setCliProperty( defStrs[i] );
448             }
449         }
450     }
451 
452     /**
453      * Set Jelly rootContext.
454      * 
455      * @param rootContext
456      *            The mavenSession jelly rootContext.
457      */
458     public void setRootContext( MavenJellyContext rootContext )
459     {
460         this.rootContext = rootContext;
461     }
462 
463     /**
464      * Display helpful information regarding all documented goals.
465      */
466     protected void displayGoals()
467     {
468         displayGoals( false, null );
469     }
470 
471     /**
472      * Display helpful information regarding all documented goals.
473      * 
474      * @param pluginOnly
475      *            show information for the given plugin only
476      * @param plugin
477      *            plugin to show info for
478      */
479     protected void displayGoals( boolean pluginOnly, String plugin )
480     {
481         String title = MavenUtils.getMessage( "displayGoals.title" );
482         if ( pluginOnly )
483         {
484             title =
485                 ( plugin == null ? MavenUtils.getMessage( "displayGoals.title.pluginOnly.null" )
486                                 : MavenUtils.getMessage( "displayGoals.title.pluginOnly.notNull" ) + plugin );
487         }
488         LOGGER.info( title );
489         LOGGER.info( format( "", title.length(), '=' ) );
490 
491         Set goals = mavenSession.getAllGoalNames();
492         displayGoals( pluginOnly, plugin, goals );
493     }
494 
495     /**
496      * To allow subclasses stop the app from exiting
497      * 
498      * @param status
499      *            the value to exit with
500      */
501     protected void exit( int status )
502     {
503         System.exit( status );
504     }
505 
506     /**
507      * Produce a formatted/padded string.
508      * 
509      * @param orig
510      *            The string to format.
511      * @param width
512      *            The width of the resulting formatted string.
513      * @param pad
514      *            The trailing pad character.
515      * 
516      * @return The formatted string, or the original string if the length is already &gt;= <code>width</code>.
517      */
518     protected String format( String orig, int width, char pad )
519     {
520         if ( orig.length() >= width )
521         {
522             return orig;
523         }
524 
525         StringBuffer buf = new StringBuffer().append( orig );
526 
527         int diff = width - orig.length();
528 
529         for ( int i = 0; i < diff; ++i )
530         {
531             buf.append( pad );
532         }
533 
534         return buf.toString();
535     }
536 
537     /**
538      * Get the CLI parser.
539      * 
540      * @return CommandLine The command line parser.
541      */
542     protected CommandLine getCli()
543     {
544         return this.commandLine;
545     }
546 
547     /**
548      * Initialize the IO streams.
549      * 
550      * @throws IOException
551      *             on error creating XML output and handling System.err and out
552      */
553     protected void initializeRootContext() throws IOException
554     {
555         this.writer = new OutputStreamWriter( System.out );
556         XMLOutput output = XMLOutput.createXMLOutput( writer, false );
557 
558         if ( getCli().hasOption( OPT_WORKING_DIR ) )
559         {
560             String workingDir = getCli().getOptionValue( OPT_WORKING_DIR );
561             File dir = new File( workingDir );
562             if ( !dir.isAbsolute() )
563             {
564                 workingDir = dir.getAbsolutePath();
565             }
566 
567             System.setProperty( "user.dir", workingDir );
568         }
569 
570         // We will assume here that there might not be a project.xml file present
571         // and we will create the root context from the directory gleaned from
572         // the user.dir system property.
573         File basedir = new File( System.getProperty( "user.dir" ) );
574         MavenJellyContext c = MavenUtils.createContext( basedir );
575         setRootContext( c );
576         c.setXMLOutput( output );
577 
578         // This is just a start at this mode - there'll still be some output
579         if ( getCli().hasOption( OPT_QUIET ) )
580         {
581             // A little bit of log4j specifics needed here
582             Logger.getLogger( "org.apache.maven" ).setLevel( Level.ERROR );
583         }
584 
585         // -X takes precedence over -q
586         if ( getCli().hasOption( OPT_DEBUG ) )
587         {
588             getRootContext().setDebugOn( Boolean.TRUE );
589             // A little bit of log4j specifics needed here
590             Logger.getLogger( "org.apache.maven" ).setLevel( Level.DEBUG );
591         }
592         else
593         {
594             getRootContext().setDebugOn( Boolean.FALSE );
595         }
596 
597         if ( getCli().hasOption( OPT_EMACS_OUTPUT ) )
598         {
599             getRootContext().setEmacsModeOn( Boolean.TRUE );
600         }
601         else
602         {
603             getRootContext().setEmacsModeOn( Boolean.FALSE );
604         }
605 
606         if ( getCli().hasOption( OPT_WORK_OFFLINE ) )
607         {
608             System.setProperty( MavenConstants.ONLINE, "false" );
609         }
610     }
611 
612     /**
613      * Prints the MavenSession header.
614      */
615     protected void printConsoleMavenHeader()
616     {
617         Properties p = new Properties();
618         InputStream is = getClass().getResourceAsStream( "/driver.properties" );
619         try
620         {
621             p.load( is );
622         }
623         catch ( IOException e )
624         {
625             LOGGER.error( MavenUtils.getMessage( "printConsoleMavenHeader.error" ) + e );
626         }
627         finally
628         {
629             try
630             {
631                 is.close();
632             }
633             catch ( IOException e )
634             {
635                 LOGGER.debug( "WARNING: Cannot close stream!", e );
636             }
637         }
638 
639         LOGGER.info( " __  __" );
640         LOGGER.info( "|  \\/  |__ _Apache__ ___" );
641         LOGGER.info( "| |\\/| / _` \\ V / -_) ' \\  ~ intelligent projects ~" );
642         LOGGER.info( "|_|  |_\\__,_|\\_/\\___|_||_|  v. " + p.getProperty( "maven.application.version" ) );
643         LOGGER.info( "" );
644 
645     }
646 
647     /**
648      * Set the cli parser.
649      * 
650      * @param commandLine
651      *            The command line parser.
652      */
653     protected void setCli( CommandLine commandLine )
654     {
655         this.commandLine = commandLine;
656     }
657 
658     /**
659      * Nicely wraps a message for console output, using a word BreakIterator instance to determine wrapping breaks.
660      * 
661      * @param msg
662      *            the string message for the console
663      * @param wrapIndent
664      *            the number of characters to indent all lines after the first one
665      * @param lineWidth
666      *            the console width that determines where to wrap
667      * @return the message wrapped for the console
668      */
669     protected String wrapConsoleMessage( String msg, int wrapIndent, int lineWidth )
670     {
671         if ( ( msg.indexOf( '\n' ) < 0 ) && ( msg.indexOf( '\r' ) < 0 ) )
672         {
673             return wrapConsoleLine( msg, wrapIndent, lineWidth );
674         }
675 
676         StringBuffer buf = new StringBuffer();
677         StringTokenizer tok = new StringTokenizer( msg, "\n\r" );
678         while ( tok.hasMoreTokens() )
679         {
680             String token = tok.nextToken().trim();
681             if ( token.length() > 0 )
682             {
683                 buf.append( wrapConsoleLine( token, wrapIndent, lineWidth ) );
684                 buf.append( LS );
685             }
686         }
687         return buf.toString();
688     }
689 
690     /**
691      * Customize the log4j configuration from properties (read in system, user or project scope).
692      */
693     private void customizeLogging()
694     {
695         Iterator iter = getRootContext().getVariableNames();
696         Properties log4jProperties = new Properties();
697         String propertyName;
698         while ( iter.hasNext() )
699         {
700             propertyName = (String) iter.next();
701             if ( propertyName.startsWith( "log4j" ) )
702             {
703                 log4jProperties.put( propertyName, getRootContext().getVariable( propertyName ) );
704             }
705         }
706         // Modify the default log4j settings
707         PropertyConfigurator.configure( log4jProperties );
708         log4jProperties = null;
709         iter = null;
710     }
711 
712     /**
713      * Display information on how to file a bug report if an unknown error occurs.
714      */
715     private void displayBugReportHelp()
716     {
717         LOGGER.info( MavenUtils.getMessage( "line" ) );
718         StringBuffer sb = new StringBuffer();
719         sb.append( MavenUtils.getMessage( "displayBugReportHelp.line1" ) ).append( '\n' );
720         sb.append( MavenUtils.getMessage( "displayBugReportHelp.line2" ) ).append( '\n' );
721         sb.append( MavenUtils.getMessage( "displayBugReportHelp.line3", LINKS_PROPERTIES.get( "faqUrl" ) ) ).append(
722                                                                                                                      '\n' );
723         sb.append( MavenUtils.getMessage( "displayBugReportHelp.line4" ) ).append( '\n' );
724         sb.append( MavenUtils.getMessage( "displayBugReportHelp.line5", LINKS_PROPERTIES.get( "usersMailingListUrl" ) ) ).append(
725                                                                                                                                   '\n' );
726         sb.append( MavenUtils.getMessage( "displayBugReportHelp.line6", LINKS_PROPERTIES.get( "issueTrackingUrl" ) ) ).append(
727                                                                                                                                '\n' );
728         sb.append( MavenUtils.getMessage( "displayBugReportHelp.line7" ) );
729 
730         LOGGER.info( "" );
731         LOGGER.info( sb.toString() );
732         LOGGER.info( "" );
733     }
734 
735     private void displayBuildFailed( Throwable t, boolean displayBugReportHelp, boolean displayErrors,
736                                      boolean displayStackTrace )
737     {
738         if ( displayBugReportHelp )
739             displayBugReportHelp();
740         if ( displayErrors )
741             displayThrowable( t, displayStackTrace );
742         LOGGER.warn( MavenUtils.getMessage( "line" ) );
743         LOGGER.warn( MavenUtils.getMessage( "build.failed" ) );
744     }
745 
746     /**
747      * Display helpful information about the given default goal.
748      * 
749      * @param goalName
750      *            goal to show info for
751      * @param goalDescription
752      *            the description of the goal
753      * @param newLine
754      *            whether to append a newline
755      */
756     private void displayDefaultGoal( String goalName, String goalDescription, boolean newLine )
757     {
758         if ( "".equals( goalName ) )
759         {
760             return;
761         }
762 
763         String msgPrefix = format( "[" + goalName + "]", WRAP_INDENT, ' ' ) + " ";
764         if ( goalDescription == null )
765         {
766             goalDescription = "( NO DEFAULT GOAL )";
767         }
768 
769         if ( newLine )
770         {
771             msgPrefix = LS + msgPrefix;
772         }
773 
774         LOGGER.info( msgPrefix + wrapConsoleMessage( goalDescription, WRAP_INDENT + 1, CONSOLE_WIDTH ) );
775     }
776 
777     /**
778      * Display helpful information about the given goal.
779      * 
780      * @param goalName
781      *            goal to show info for
782      * @param goalDescription
783      *            the description of the goal
784      */
785     private void displayGoal( String goalName, String goalDescription )
786     {
787         if ( "".equals( goalName ) )
788         {
789             goalName = "( NO GOAL )";
790         }
791 
792         String msgPrefix = format( "  " + goalName + "  ", WRAP_INDENT, '.' ) + " ";
793 
794         LOGGER.info( msgPrefix + wrapConsoleMessage( goalDescription, WRAP_INDENT + 1, CONSOLE_WIDTH ) );
795     }
796 
797     /**
798      * Display helpful information regarding all documented goals.
799      * 
800      * @param pluginOnly
801      *            show information for the given plugin only
802      * @param plugin
803      *            plugin to show info for
804      * @param goals
805      *            the set of goals
806      */
807     private void displayGoals( boolean pluginOnly, String plugin, Set goals )
808     {
809         Map map = new HashMap();
810         for ( Iterator i = goals.iterator(); i.hasNext(); )
811         {
812             String goal = (String) i.next();
813             String pluginName = "";
814             int index = goal.indexOf( ':' );
815             if ( index >= 0 )
816             {
817                 pluginName = goal.substring( 0, index );
818             }
819             List l = (List) map.get( pluginName );
820             if ( l == null )
821             {
822                 l = new ArrayList();
823                 map.put( pluginName, l );
824             }
825             l.add( goal.substring( index + 1 ) );
826         }
827 
828         List goalList = (List) map.get( "" );
829         if ( goalList != null )
830         {
831             for ( Iterator i = goalList.iterator(); i.hasNext(); )
832             {
833                 String goal = (String) i.next();
834                 if ( map.containsKey( goal ) )
835                 {
836                     i.remove();
837                     List pluginGoals = (List) map.get( goal );
838                     if ( pluginGoals == null )
839                     {
840                         pluginGoals = new ArrayList();
841                         map.put( goal, pluginGoals );
842                     }
843                     // don't add the empty goal as we always attempt to display the default
844                 }
845             }
846         }
847 
848         List keys = new ArrayList( map.keySet() );
849         Collections.sort( keys );
850 
851         List undocumentedGoals = new ArrayList();
852 
853         for ( Iterator i = keys.iterator(); i.hasNext(); )
854         {
855             String pluginName = (String) i.next();
856             if ( pluginOnly )
857             {
858                 if ( plugin == null )
859                 {
860                     // only show default goal
861                     displayDefaultGoal( pluginName, mavenSession.getGoalDescription( pluginName ), false );
862                     continue;
863                 }
864                 else if ( !pluginName.equals( plugin ) )
865                 {
866                     continue;
867                 }
868             }
869 
870             displayDefaultGoal( pluginName, mavenSession.getGoalDescription( pluginName ), true );
871             List l = (List) map.get( pluginName );
872             Collections.sort( l );
873             for ( Iterator j = l.iterator(); j.hasNext(); )
874             {
875                 String goalName = (String) j.next();
876                 String fullGoalName = pluginName.length() == 0 ? goalName : pluginName + ":" + goalName;
877                 String goalDescription = mavenSession.getGoalDescription( fullGoalName );
878                 if ( goalDescription != null )
879                 {
880                     displayGoal( goalName, goalDescription );
881                 }
882                 else
883                 {
884                     undocumentedGoals.add( fullGoalName );
885                 }
886             }
887         }
888 
889         if ( undocumentedGoals.isEmpty() == false )
890         {
891             displayGoalsWithoutDescriptions( undocumentedGoals );
892         }
893         LOGGER.info( "" );
894     }
895 
896     /**
897      * Display goals without descriptions.
898      * 
899      * @param list
900      *            List of undocument goal names.
901      */
902     private void displayGoalsWithoutDescriptions( List list )
903     {
904         LOGGER.info( "" );
905         LOGGER.info( MavenUtils.getMessage( "displayGoalsWithoutDescriptions.info" ) );
906         LOGGER.info( "" );
907 
908         for ( Iterator i = list.iterator(); i.hasNext(); )
909         {
910             String goalName = (String) i.next();
911 
912             LOGGER.info( "  " + goalName );
913         }
914     }
915 
916     /**
917      * Display the command line help if the option is present, then exit
918      */
919     private void displayHelp()
920     {
921         if ( getCli().hasOption( OPT_DISPLAY_HELP ) )
922         {
923             CLIManager.displayHelp();
924             LOGGER.info( "" );
925             exit( RC_OK );
926         }
927     }
928 
929     /**
930      * Display the command line info if the option is present, then exit
931      */
932     private void displayInfo()
933     {
934         if ( getCli().hasOption( OPT_DISPLAY_INFO ) )
935         {
936             CLIManager.displayInfo();
937             LOGGER.info( "" );
938             LOGGER.info( MavenUtils.getMessage( "displayInfo.info1" ) );
939             for ( Iterator i = mavenSession.getPluginList().iterator(); i.hasNext(); )
940             {
941                 LOGGER.info( "  " + i.next() );
942             }
943 
944             Properties buildProperties = new Properties();
945             FileInputStream fis = null;
946             try
947             {
948                 fis = new FileInputStream( System.getProperty( "user.home" ) + "/build.properties" );
949                 buildProperties.load( fis );
950             }
951             catch ( IOException e )
952             {
953                 LOGGER.error( MavenUtils.getMessage( "displayInfo.error" ) + e.getMessage() );
954             }
955             finally
956             {
957                 if ( fis != null )
958                 {
959                     try
960                     {
961                         fis.close();
962                     }
963                     catch ( IOException e )
964                     {
965                         LOGGER.debug( "WARNING: Cannot close stream!", e );
966                     }
967                     fis = null;
968                 }
969             }
970             LOGGER.info( MavenUtils.getMessage( "displayInfo.info2" ) + buildProperties );
971             exit( RC_OK );
972         }
973     }
974 
975     /**
976      * Display the plugin help if the option is present, then exit.
977      * 
978      * @throws MavenException
979      *             when anything goes wrong
980      */
981     private void displayPluginHelp() throws MavenException
982     {
983         if ( getCli().hasOption( OPT_DISPLAY_PLUGIN_HELP ) )
984         {
985             String plugin = getCli().getOptionValue( OPT_DISPLAY_PLUGIN_HELP );
986 
987             displayGoals( true, plugin );
988 
989             if ( plugin != null )
990             {
991                 Project project = mavenSession.getPluginProjectFromGoal( plugin );
992                 if ( ( project != null ) && ( project.getDescription() != null ) )
993                 {
994                     LOGGER.info( wrapConsoleMessage( project.getDescription(), 0, CONSOLE_WIDTH ) );
995                 }
996             }
997 
998             exit( RC_OK );
999         }
1000     }
1001 
1002     /**
1003      * Display the project help if the option is present, then exit.
1004      * 
1005      * @throws MavenException
1006      *             when anything goes wrong
1007      */
1008     private void displayProjectHelp() throws MavenException
1009     {
1010         if ( getCli().hasOption( OPT_DISPLAY_USAGE ) )
1011         {
1012             Set goals = mavenSession.getProjectGoals( mavenSession.getRootProject() );
1013 
1014             String title = MavenUtils.getMessage( "displayProjectHelp.title" );
1015             LOGGER.info( title );
1016             LOGGER.info( format( "", title.length(), '=' ) );
1017             LOGGER.info( "" );
1018 
1019             Project rootProject = mavenSession.getRootProject();
1020             if ( ( rootProject.getBuild() != null ) && ( rootProject.getBuild().getDefaultGoal() != null ) )
1021             {
1022                 String defaultGoal = rootProject.getBuild().getDefaultGoal();
1023                 String msg = MavenUtils.getMessage( "displayGoals.defaultGoal" ) + defaultGoal;
1024                 LOGGER.info( wrapConsoleMessage( msg, WRAP_INDENT + 1, CONSOLE_WIDTH ) );
1025             }
1026 
1027             displayGoals( false, null, goals );
1028 
1029             String msg = mavenSession.getRootProject().getDescription();
1030             if ( msg != null )
1031             {
1032                 LOGGER.info( wrapConsoleMessage( msg, 0, CONSOLE_WIDTH ) );
1033             }
1034 
1035             exit( RC_OK );
1036         }
1037     }
1038 
1039     private void displayThrowable( Throwable t, boolean displayStackTrace )
1040     {
1041         LOGGER.info( MavenUtils.getMessage( "line" ) );
1042         if ( displayStackTrace )
1043             LOGGER.info( MavenUtils.getMessage( "build.errors.stack" ) );
1044         Throwable localThrowable = t;
1045         while ( localThrowable != null )
1046         {
1047             if ( localThrowable instanceof JellyException )
1048             {
1049                 JellyException jellyEx = (JellyException) localThrowable;
1050                 if ( jellyEx.getCause() == null )
1051                 {
1052                     LOGGER.info( MavenUtils.getMessage( "exception.cause" ) + jellyEx.getReason() );
1053                 }
1054                 if ( displayStackTrace )
1055                 {
1056                     LOGGER.info( MavenUtils.getMessage( "build.jellyException.file", jellyEx.getFileName() ) );
1057                     LOGGER.info( MavenUtils.getMessage( "build.jellyException.element", jellyEx.getElementName() ) );
1058                     LOGGER.info( MavenUtils.getMessage( "build.jellyException.line",
1059                                                         Integer.toString( jellyEx.getLineNumber() ) ) );
1060                     LOGGER.info( MavenUtils.getMessage( "build.jellyException.column",
1061                                                         Integer.toString( jellyEx.getColumnNumber() ) ) );
1062                 }
1063             }
1064             else if ( localThrowable.getLocalizedMessage() != null )
1065                 LOGGER.info( MavenUtils.getMessage( "exception.cause" ) + localThrowable.getLocalizedMessage() );
1066             else
1067                 LOGGER.info( MavenUtils.getMessage( "exception.cause" ) + localThrowable.getClass().getName() );
1068 
1069             localThrowable = localThrowable.getCause();
1070         }
1071         if ( displayStackTrace )
1072         {
1073             LOGGER.info( "" );
1074             LOGGER.info( MavenUtils.getMessage( "build.stacktrace" ), t );
1075         }
1076     }
1077 
1078     /**
1079      * Display the Maven version info if the option is present, then exit
1080      */
1081     private void displayVersion()
1082     {
1083         if ( getCli().hasOption( OPT_DISPLAY_VERSION ) )
1084         {
1085             printConsoleMavenHeader();
1086             exit( RC_OK );
1087         }
1088     }
1089 
1090     /**
1091      * From the CWD search through the directory hierarchy for an XML-based POM.
1092      * 
1093      * @param start
1094      *            The starting directory for the POM search.
1095      * @param suffix
1096      *            The suffix for the file to be searched for.
1097      * @return The found project.xml file.
1098      */
1099     private File find( File start, String suffix )
1100     {
1101         if ( start == null )
1102         {
1103             return null;
1104         }
1105 
1106         File dir = start.getAbsoluteFile();
1107         File file = new File( dir, suffix );
1108 
1109         return file.exists() ? file : find( dir.getParentFile(), suffix );
1110     }
1111 
1112     /**
1113      * From the CWD search through the directory hierarchy for an XML-based POM.
1114      * 
1115      * @param filename
1116      *            The filename to find.
1117      * @return The found file.
1118      */
1119     private File find( String filename )
1120     {
1121         // An empty string should resolve to the current directory (user.dir)
1122         return find( new File( "" ), filename );
1123     }
1124 
1125     /**
1126      * Get the project descriptor file.
1127      * 
1128      * @return The project descriptor file.
1129      * @throws IOException
1130      *             when the project.xml parent can't be resolved
1131      */
1132     private File getDescriptorFile() throws IOException
1133     {
1134         File descriptorFile = null;
1135         String descriptorName = null;
1136 
1137         // Determine what the name of the XML POM descriptor is. If it is
1138         // specified then we will what the user requests, otherwise we will
1139         // default to using 'project.xml'.
1140         if ( getCli().hasOption( OPT_SET_POM_DESCRIPTOR ) )
1141         {
1142             descriptorName = getCli().getOptionValue( OPT_SET_POM_DESCRIPTOR );
1143         }
1144         else
1145         {
1146             descriptorName = POM_FILE_NAME;
1147         }
1148 
1149         if ( getCli().hasOption( OPT_FIND_POM_DESCRIPTOR ) )
1150         {
1151             descriptorFile = find( descriptorName );
1152 
1153             if ( descriptorFile == null )
1154             {
1155                 descriptorFile = new File( descriptorName );
1156             }
1157         }
1158         else
1159         {
1160             descriptorFile = new File( descriptorName );
1161         }
1162         if ( getCli().hasOption( OPT_SET_POM_DESCRIPTOR ) && !descriptorFile.exists() )
1163         {
1164             throw new FileNotFoundException( "Project file '" + descriptorName + "' not found" );
1165         }
1166 
1167         descriptorFile = descriptorFile.getAbsoluteFile();
1168         if ( !getCli().hasOption( OPT_WORKING_DIR ) )
1169         {
1170             System.setProperty( "user.dir", descriptorFile.getParentFile().getCanonicalPath() );
1171         }
1172 
1173         return descriptorFile;
1174     }
1175 
1176     /**
1177      * Intialize main and exit if failures occur
1178      * 
1179      * @param args
1180      *            command line args
1181      */
1182     private void initializeMain( String[] args )
1183     {
1184         int returnCode = RC_OK;
1185 
1186         try
1187         {
1188             initialize( args );
1189         }
1190         catch ( ParseException e )
1191         {
1192             LOGGER.info( e.getLocalizedMessage() );
1193             CLIManager.displayHelp();
1194             returnCode = RC_BAD_ARG;
1195         }
1196         catch ( IOException e )
1197         {
1198             LOGGER.info( e.getLocalizedMessage() );
1199             returnCode = RC_INIT_ERROR;
1200         }
1201         catch ( Exception e )
1202         {
1203             e.printStackTrace();
1204             returnCode = RC_INIT_ERROR;
1205         }
1206 
1207         if ( returnCode != RC_OK )
1208         {
1209             LOGGER.info( "" );
1210             exit( returnCode );
1211         }
1212 
1213     }
1214 
1215     /**
1216      * Initialize the mavenSession bean.
1217      * 
1218      * @throws IOException
1219      *             when the descriptor file parent can't be resolved
1220      */
1221     private void initializeMavenSession() throws IOException
1222     {
1223         // Even though the rootProject contains the rootContext we set both in
1224         // the even that there is no rootProject and the user is requesting goals
1225         // to be attained that do not directly relate to a project. A goaly that
1226         // may, say, generate the skeletal maven application build.
1227 
1228         mavenSession = new MavenSession();
1229         mavenSession.setRootContext( getRootContext() );
1230         // note: setRootDescriptor file is a static method
1231         MavenSession.setRootDescriptorFile( getDescriptorFile() );
1232     }
1233 
1234     /**
1235      * Set a property based upon a commandline <code>name=value</code> string.
1236      * 
1237      * @param defStr
1238      *            The <code>name=value</code> string.
1239      */
1240     private void setCliProperty( String defStr )
1241     {
1242         String name = null;
1243         String value = null;
1244         int equalLoc = defStr.indexOf( "=" );
1245 
1246         if ( equalLoc <= 0 )
1247         {
1248             name = defStr.trim();
1249             value = "true";
1250         }
1251         else
1252         {
1253             name = defStr.substring( 0, equalLoc ).trim();
1254             value = defStr.substring( equalLoc + 1 ).trim();
1255         }
1256 
1257         if ( name.length() > 0 )
1258         {
1259             System.setProperty( name, value );
1260         }
1261     }
1262 
1263     /**
1264      * Reformat a message to display on the console.
1265      * 
1266      * @param msg
1267      *            the message to display
1268      * @param wrapIndent
1269      *            indent
1270      * @param lineWidth
1271      *            line width
1272      * @return String
1273      */
1274     private String wrapConsoleLine( String msg, int wrapIndent, int lineWidth )
1275     {
1276         int offset = lineWidth - wrapIndent;
1277         if ( msg.length() <= offset )
1278         {
1279             return msg;
1280         }
1281 
1282         BreakIterator bIter = BreakIterator.getWordInstance();
1283         StringBuffer buf = new StringBuffer();
1284         String pad = " ";
1285         int currentPos = 0;
1286         bIter.setText( msg );
1287 
1288         while ( offset < bIter.getText().getEndIndex() )
1289         {
1290             if ( Character.isWhitespace( bIter.getText().first() ) )
1291             {
1292                 // remove leading whitespace and continue
1293                 msg = msg.substring( 1 );
1294                 bIter.setText( msg );
1295                 continue;
1296             }
1297 
1298             // get the last boundary before the specified offset
1299             currentPos = bIter.preceding( offset );
1300             // append from the start to currentPos
1301             buf.append( msg.substring( 0, currentPos ) );
1302 
1303             // start next line
1304             buf.append( LS );
1305 
1306             // pad with spaces to create indent
1307             for ( int i = 0; ( i != wrapIndent ) && ( i < lineWidth ); i++ )
1308             {
1309                 buf.append( pad );
1310             }
1311 
1312             // set the text of the break iterator to be the rest
1313             // of the string not already appended
1314             msg = msg.substring( currentPos );
1315 
1316             // reset the text for another go
1317             bIter.setText( msg );
1318         }
1319 
1320         // remove leading whitespace and continue
1321         while ( Character.isWhitespace( msg.charAt( 0 ) ) )
1322         {
1323             msg = msg.substring( 1 );
1324         }
1325         buf.append( msg );
1326         return buf.toString();
1327     }
1328 }