View Javadoc
1   package org.codehaus.plexus.tools.cli;
2   
3   /*
4    * Copyright 2006 The Codehaus Foundation.
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *      http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  import org.apache.commons.cli.CommandLine;
20  import org.apache.commons.cli.CommandLineParser;
21  import org.apache.commons.cli.GnuParser;
22  import org.apache.commons.cli.HelpFormatter;
23  import org.apache.commons.cli.OptionBuilder;
24  import org.apache.commons.cli.Options;
25  import org.apache.commons.cli.ParseException;
26  import org.codehaus.plexus.ContainerConfiguration;
27  import org.codehaus.plexus.DefaultContainerConfiguration;
28  import org.codehaus.plexus.DefaultPlexusContainer;
29  import org.codehaus.plexus.PlexusContainer;
30  import org.codehaus.plexus.PlexusContainerException;
31  import org.codehaus.plexus.classworlds.ClassWorld;
32  import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
33  
34  import java.io.IOException;
35  import java.io.InputStream;
36  import java.util.ArrayList;
37  import java.util.List;
38  import java.util.Properties;
39  
40  /**
41   * @author jason van zyl
42   * @version $Id$
43   * @noinspection UseOfSystemOutOrSystemErr,ACCESS_STATIC_VIA_INSTANCE
44   */
45  public abstract class AbstractCli
46      implements Cli
47  {
48      // ----------------------------------------------------------------------------
49      // These are standard options that we would want to use for all our projects.
50      // ----------------------------------------------------------------------------
51  
52      public static final char QUIET = 'q';
53  
54      public static final char DEBUG = 'X';
55  
56      public static final char ERRORS = 'e';
57  
58      public static final char HELP = 'h';
59  
60      public static final char VERSION = 'v';
61  
62      public static final char SET_SYSTEM_PROPERTY = 'D';
63  
64      // ----------------------------------------------------------------------------
65      // Abstract methods
66      // ----------------------------------------------------------------------------
67  
68      public abstract Options buildCliOptions( Options options );
69  
70      public abstract void invokePlexusComponent( CommandLine cli,
71                                                  PlexusContainer container )
72          throws Exception;
73  
74      public String getPomPropertiesPath()
75      {
76          return null;
77      }
78  
79      public int execute( String[] args )
80      {
81          ClassWorld classWorld = new ClassWorld( "plexus.core", Thread.currentThread().getContextClassLoader() );
82  
83          return execute( args, classWorld );
84      }
85  
86      public int execute( String[] args,
87                          ClassWorld classWorld )
88      {
89          CommandLine cli;
90  
91          try
92          {
93              cli = parse( args );
94          }
95          catch ( ParseException e )
96          {
97              System.err.println( "Unable to parse command line options: " + e.getMessage() );
98  
99              displayHelp();
100 
101             return 1;
102         }
103 
104         if ( System.getProperty( "java.class.version", "44.0" ).compareTo( "48.0" ) < 0 )
105         {
106             System.err.println( "Sorry, but JDK 1.4 or above is required to execute Maven" );
107 
108             System.err.println(
109                 "You appear to be using Java version: " + System.getProperty( "java.version", "<unknown>" ) );
110 
111             return 1;
112         }
113 
114         boolean debug = cli.hasOption( DEBUG );
115 
116         boolean quiet = !debug && cli.hasOption( QUIET );
117 
118         boolean showErrors = debug || cli.hasOption( ERRORS );
119 
120         if ( showErrors )
121         {
122             System.out.println( "+ Error stacktraces are turned on." );
123         }
124 
125         // ----------------------------------------------------------------------------
126         // Logging
127         // ----------------------------------------------------------------------------
128 
129         int loggingLevel;
130 
131         if ( debug )
132         {
133             loggingLevel = 0;
134         }
135         else if ( quiet )
136         {
137             loggingLevel = 0;
138         }
139         else
140         {
141             loggingLevel = 0;
142         }
143 
144         // ----------------------------------------------------------------------
145         // Process particular command line options
146         // ----------------------------------------------------------------------
147 
148         if ( cli.hasOption( HELP ) )
149         {
150             displayHelp();
151 
152             return 0;
153         }
154 
155         if ( cli.hasOption( VERSION ) )
156         {
157             showVersion();
158 
159             return 0;
160         }
161         else if ( debug )
162         {
163             showVersion();
164         }
165 
166         // ----------------------------------------------------------------------------
167         // This is what we will generalize for the invocation of the command line.
168         // ----------------------------------------------------------------------------
169 
170         try
171         {
172             ContainerConfiguration configuration = new DefaultContainerConfiguration()
173                 .setClassWorld( classWorld );
174 
175             customizeContainerConfiguration( configuration, cli );
176             
177             PlexusContainer plexus = new DefaultPlexusContainer( configuration );
178 
179             invokePlexusComponent( cli, plexus );
180         }
181         catch ( PlexusContainerException e )
182         {
183             showFatalError( "Cannot create Plexus container.", e, true );
184         }
185         catch ( ComponentLookupException e )
186         {
187             showError( "Cannot lookup application component.", e, true );
188         }
189         catch ( Exception e )
190         {
191             showError( "Problem executing command line.", e, true );
192         }
193 
194         return 0;
195     }
196 
197     protected void customizeContainerConfiguration( ContainerConfiguration configuration, CommandLine cli )
198     {        
199     }
200     
201     protected int showFatalError( String message,
202                                   Exception e,
203                                   boolean show )
204     {
205         System.err.println( "FATAL ERROR: " + message );
206 
207         if ( show )
208         {
209             System.err.println( "Error stacktrace:" );
210 
211             e.printStackTrace();
212         }
213         else
214         {
215             System.err.println( "For more information, run with the -e flag" );
216         }
217 
218         return 1;
219     }
220 
221     protected void showError( String message,
222                               Exception e,
223                               boolean show )
224     {
225         System.err.println( message );
226 
227         if ( show )
228         {
229             System.err.println( "Error stacktrace:" );
230 
231             e.printStackTrace();
232         }
233     }
234 
235     // Need to get the versions of the application in a general way, so that I need a way to get the
236     // specifics of the application so that I can do this in a general way.
237     private void showVersion()
238     {
239         InputStream is;
240 
241         try
242         {
243             Properties properties = new Properties();
244 
245             String pomPropertiesPath = getPomPropertiesPath();
246 
247             if ( pomPropertiesPath == null )
248             {
249                 System.err.println( "Unable determine version from JAR file." );
250 
251                 return;
252             }
253 
254             is = AbstractCli.class.getClassLoader().getResourceAsStream( pomPropertiesPath );
255 
256             if ( is == null )
257             {
258                 System.err.println( "Unable determine version from JAR file." );
259 
260                 return;
261             }
262 
263             properties.load( is );
264 
265             if ( properties.getProperty( "builtOn" ) != null )
266             {
267                 System.out.println( "Version: " + properties.getProperty( "version", "unknown" ) + " built on " +
268                     properties.getProperty( "builtOn" ) );
269             }
270             else
271             {
272                 System.out.println( "Version: " + properties.getProperty( "version", "unknown" ) );
273             }
274         }
275         catch ( IOException e )
276         {
277             System.err.println( "Unable determine version from JAR file: " + e.getMessage() );
278         }
279     }
280 
281     // ----------------------------------------------------------------------
282     // System properties handling
283     // ----------------------------------------------------------------------
284 
285     private Properties getExecutionProperties( CommandLine commandLine )
286     {
287         Properties executionProperties = new Properties();
288 
289         // ----------------------------------------------------------------------
290         // Options that are set on the command line become system properties
291         // and therefore are set in the session properties. System properties
292         // are most dominant.
293         // ----------------------------------------------------------------------
294 
295         if ( commandLine.hasOption( SET_SYSTEM_PROPERTY ) )
296         {
297             String[] defStrs = commandLine.getOptionValues( SET_SYSTEM_PROPERTY );
298 
299             for ( int i = 0; i < defStrs.length; ++i )
300             {
301                 setCliProperty( defStrs[i], executionProperties );
302             }
303         }
304 
305         executionProperties.putAll( System.getProperties() );
306 
307         return executionProperties;
308     }
309 
310     private void setCliProperty( String property,
311                                  Properties executionProperties )
312     {
313         String name;
314 
315         String value;
316 
317         int i = property.indexOf( "=" );
318 
319         if ( i <= 0 )
320         {
321             name = property.trim();
322 
323             value = "true";
324         }
325         else
326         {
327             name = property.substring( 0, i ).trim();
328 
329             value = property.substring( i + 1 ).trim();
330         }
331 
332         executionProperties.setProperty( name, value );
333 
334         // ----------------------------------------------------------------------
335         // I'm leaving the setting of system properties here as not to break
336         // the SystemPropertyProfileActivator. This won't harm embedding. jvz.
337         // ----------------------------------------------------------------------
338 
339         System.setProperty( name, value );
340     }
341 
342     private Options options;
343 
344     public Options buildDefaultCliOptions()
345     {
346         options = new Options();
347 
348         options.addOption(
349             OptionBuilder.withLongOpt( "define" ).hasArg().withDescription( "Define a system property" ).create(
350                 SET_SYSTEM_PROPERTY ) );
351         options.addOption(
352             OptionBuilder.withLongOpt( "help" ).withDescription( "Display help information" ).create( HELP ) );
353         options.addOption(
354             OptionBuilder.withLongOpt( "version" ).withDescription( "Display version information" ).create( VERSION ) );
355         options.addOption(
356             OptionBuilder.withLongOpt( "quiet" ).withDescription( "Quiet output - only show errors" ).create( QUIET ) );
357         options.addOption(
358             OptionBuilder.withLongOpt( "debug" ).withDescription( "Produce execution debug output" ).create( DEBUG ) );
359         options.addOption(
360             OptionBuilder.withLongOpt( "errors" ).withDescription( "Produce execution error messages" ).create(
361                 ERRORS ) );
362 
363         return buildCliOptions( options );
364     }
365 
366     public CommandLine parse( String[] args )
367         throws ParseException
368     {
369         // We need to eat any quotes surrounding arguments...
370         String[] cleanArgs = cleanArgs( args );
371 
372         CommandLineParser parser = new GnuParser();
373 
374         return parser.parse( buildDefaultCliOptions(), cleanArgs );
375     }
376 
377     private static String[] cleanArgs( String[] args )
378     {
379         List cleaned = new ArrayList();
380 
381         StringBuffer currentArg = null;
382 
383         for ( int i = 0; i < args.length; i++ )
384         {
385             String arg = args[i];
386 
387             boolean addedToBuffer = false;
388 
389             if ( arg.startsWith( "\"" ) )
390             {
391                 // if we're in the process of building up another arg, push it and start over.
392                 // this is for the case: "-Dfoo=bar "-Dfoo2=bar two" (note the first unterminated quote)
393                 if ( currentArg != null )
394                 {
395                     cleaned.add( currentArg.toString() );
396                 }
397 
398                 // start building an argument here.
399                 currentArg = new StringBuffer( arg.substring( 1 ) );
400 
401                 addedToBuffer = true;
402             }
403 
404             // this has to be a separate "if" statement, to capture the case of: "-Dfoo=bar"
405             if ( arg.endsWith( "\"" ) )
406             {
407                 String cleanArgPart = arg.substring( 0, arg.length() - 1 );
408 
409                 // if we're building an argument, keep doing so.
410                 if ( currentArg != null )
411                 {
412                     // if this is the case of "-Dfoo=bar", then we need to adjust the buffer.
413                     if ( addedToBuffer )
414                     {
415                         currentArg.setLength( currentArg.length() - 1 );
416                     }
417                     // otherwise, we trim the trailing " and append to the buffer.
418                     else
419                     {
420                         // TODO: introducing a space here...not sure what else to do but collapse whitespace
421                         currentArg.append( ' ' ).append( cleanArgPart );
422                     }
423 
424                     // we're done with this argument, so add it.
425                     cleaned.add( currentArg.toString() );
426                 }
427                 else
428                 {
429                     // this is a simple argument...just add it.
430                     cleaned.add( cleanArgPart );
431                 }
432 
433                 // the currentArg MUST be finished when this completes.
434                 currentArg = null;
435 
436                 continue;
437             }
438 
439             // if we haven't added this arg to the buffer, and we ARE building an argument
440             // buffer, then append it with a preceding space...again, not sure what else to
441             // do other than collapse whitespace.
442             // NOTE: The case of a trailing quote is handled by nullifying the arg buffer.
443             if ( !addedToBuffer )
444             {
445                 // append to the argument we're building, collapsing whitespace to a single space.
446                 if ( currentArg != null )
447                 {
448                     currentArg.append( ' ' ).append( arg );
449                 }
450                 // this is a loner, just add it directly.
451                 else
452                 {
453                     cleaned.add( arg );
454                 }
455             }
456         }
457 
458         // clean up.
459         if ( currentArg != null )
460         {
461             cleaned.add( currentArg.toString() );
462         }
463 
464         int cleanedSz = cleaned.size();
465         String[] cleanArgs = null;
466 
467         if ( cleanedSz == 0 )
468         {
469             // if we didn't have any arguments to clean, simply pass the original array through
470             cleanArgs = args;
471         }
472         else
473         {
474             cleanArgs = (String[]) cleaned.toArray( new String[cleanedSz] );
475         }
476 
477         return cleanArgs;
478     }
479 
480     public void displayHelp()
481     {
482         System.out.println();
483 
484         HelpFormatter formatter = new HelpFormatter();
485 
486         formatter.printHelp( "mvn [options] [<goal(s)>] [<phase(s)>]", "\nOptions:", options, "\n" );
487     }
488 }