View Javadoc

1   package org.apache.maven;
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.jelly.JellyContext;
22  import org.apache.commons.jelly.expression.CompositeExpression;
23  import org.apache.commons.jelly.expression.Expression;
24  import org.apache.commons.jelly.expression.jexl.JexlExpressionFactory;
25  import org.apache.commons.logging.Log;
26  import org.apache.commons.logging.LogFactory;
27  import org.apache.maven.jelly.JellyUtils;
28  import org.apache.maven.jelly.MavenJellyContext;
29  import org.apache.maven.project.Project;
30  import org.apache.tools.ant.DirectoryScanner;
31  import org.codehaus.plexus.util.CollectionUtils;
32  import org.codehaus.plexus.util.StringUtils;
33  import org.xml.sax.SAXException;
34  
35  import com.werken.forehead.ForeheadClassLoader;
36  
37  import java.beans.IntrospectionException;
38  import java.io.File;
39  import java.io.FileInputStream;
40  import java.io.FileNotFoundException;
41  import java.io.IOException;
42  import java.io.InputStream;
43  import java.io.StringReader;
44  import java.net.URL;
45  import java.util.ArrayList;
46  import java.util.Collection;
47  import java.util.HashMap;
48  import java.util.Iterator;
49  import java.util.List;
50  import java.util.Locale;
51  import java.util.Map;
52  import java.util.MissingResourceException;
53  import java.util.Properties;
54  import java.util.ResourceBundle;
55  import java.util.Set;
56  import java.util.StringTokenizer;
57  
58  import javax.xml.parsers.ParserConfigurationException;
59  
60  /**
61   * Utilities for reading maven project descriptors, profile descriptors and workspace descriptors.
62   * 
63   * @author <a href="mailto:jason@zenplex.com">Jason van Zyl</a>
64   * @author <a href="mailto:brett@apache.org">Brett Porter</a>
65   * 
66   * @todo Remove all the context creation code and make a ContextBuilder class.
67   * @todo [RC2] split getProject (project.properties + defaults) != getPluginProject (plugin.properties only)
68   */
69  public class MavenUtils
70  {
71      /** Log. */
72      private static final Log LOGGER = LogFactory.getLog( MavenUtils.class );
73  
74      /** Map of loaded POMs. */
75      private static HashMap parentPoms = new HashMap();
76  
77      /** Maven unknown error message. */
78      public static final String MAVEN_UNKNOWN_ERROR = "Unknown error reading project";
79  
80      /**
81       * Create a Project object given a file descriptor.
82       * 
83       * @param projectDescriptor
84       *            a maven project.xml
85       * @return the Maven project object for the given project descriptor
86       * @throws MavenException
87       *             when any errors occur
88       */
89      public static Project getProject( File projectDescriptor ) throws MavenException
90      {
91          return getProject( projectDescriptor, null );
92      }
93  
94      /**
95       * Create a Project object given a file descriptor, and a parent context
96       * 
97       * @param projectDescriptor
98       *            The file to create the project from
99       * @param parentContext
100      *            the parent Maven Jelly Context
101      * @return a new Project
102      * @throws MavenException
103      *             when any error happens.
104      */
105     public static Project getProject( File projectDescriptor, MavenJellyContext parentContext ) throws MavenException
106     {
107         return getProject( projectDescriptor, parentContext, true );
108     }
109 
110     /**
111      * Create a Project object given a file descriptor and optionally a parent Jelly context.
112      * 
113      * @param projectDescriptor
114      *            a maven project.xml {@link File}
115      * @param parentContext
116      *            the parent context for the new project
117      * @param useParentPom
118      *            whether a parent project should be respected
119      * @return the MavenSession project object for the given project descriptor
120      * @throws MavenException
121      *             when any errors occur
122      */
123     public static Project getProject( File projectDescriptor, MavenJellyContext parentContext, boolean useParentPom )
124         throws MavenException
125     {
126         Project project = null;
127         try
128         {
129             project = getNonJellyProject( projectDescriptor, parentContext, useParentPom );
130             project = getJellyProject( project );
131             project.setFile( projectDescriptor );
132 
133             // Fully initialize the project.
134             project.initialize();
135         }
136         catch ( IntrospectionException e )
137         {
138             throw new MavenException( "Error creating a string from the project", e );
139         }
140         catch ( IOException e )
141         {
142             throw new MavenException( "Error reading XML or initializing", e );
143         }
144         catch ( ParserConfigurationException e )
145         {
146             throw new MavenException( "Error creating a JAXP Parser", e );
147         }
148         catch ( SAXException e )
149         {
150             throw new MavenException( "Error parsing XML", e );
151         }
152         catch ( MavenException e )
153         {
154             throw e;
155         }
156         catch ( Exception e )
157         {
158             // FIXME
159             throw new MavenException( MAVEN_UNKNOWN_ERROR, e );
160         }
161 
162         return project;
163     }
164 
165     /**
166      * Get a project, but not a Jelly-ised project. ie Don't evaluate the variables. We are doing several things when
167      * creating a POM object, the phases are outlined here:
168      * 
169      * 1) The project.xml file is read in using betwixt which creates for us a Project object that, at this point, has
170      * not been run through Jelly i.e. no interpolation has occured yet.
171      * 
172      * 2) The context for the project is created and set. So each project manages its own context. See the
173      * createContext() method for the details context creation process.
174      * 
175      * 3) We check to see if the &lt;extend&gt; tag is being employed. If so, the parent project.xml file is read in. At
176      * this point we have a child and parent POM and the values are merged where the child's values override those of
177      * the parent.
178      * 
179      * @param projectDescriptor
180      *            the project file
181      * @param parentContext
182      *            the parent context for the new project
183      * @param useParentPom
184      *            whether a parent project should be respected
185      * @return the project
186      * @throws MavenException
187      *             when there are errors reading the descriptor
188      * @throws IOException
189      *             when resolving file names and paths
190      */
191     public static Project getNonJellyProject( File projectDescriptor, MavenJellyContext parentContext,
192                                               boolean useParentPom ) throws MavenException, IOException
193     {
194         // 1)
195         Project project = null;
196         try
197         {
198             project = new Project( projectDescriptor.toURL() );
199         }
200         catch ( Exception e )
201         {
202             throw new MavenException( "Error parsing project.xml '" + projectDescriptor.getAbsolutePath() + "'", e );
203         }
204 
205         // 2)
206         MavenJellyContext context =
207             MavenUtils.createContextNoDefaults( projectDescriptor.getParentFile(), parentContext );
208 
209         // 3)
210         String pomToExtend = project.getExtend();
211 
212         if ( ( pomToExtend != null ) && useParentPom )
213         {
214             // We must look in the <extend/> element for expressions that may be present as
215             //
216             // <extend>../project.xml</extend>
217             Expression e = JellyUtils.decomposeExpression( pomToExtend, context );
218             pomToExtend = e.evaluateAsString( context );
219             pomToExtend = MavenUtils.makeAbsolutePath( projectDescriptor.getParentFile(), pomToExtend );
220             project.setExtend( pomToExtend );
221 
222             File parentPom = new File( pomToExtend );
223             parentPom = parentPom.getCanonicalFile();
224             if ( !parentPom.exists() )
225             {
226                 throw new FileNotFoundException( "Parent POM not found: " + parentPom );
227             }
228 
229             String parentPomPath = parentPom.getPath();
230             if ( parentPomPath.equals( projectDescriptor.getCanonicalPath() ) )
231             {
232                 throw new MavenException( "Parent POM is equal to the current POM" );
233             }
234 
235             Project parent = (Project) parentPoms.get( parentPomPath );
236             if ( parent == null )
237             {
238                 parent = getNonJellyProject( parentPom, parentContext, true );
239                 parent.setFile( parentPom );
240                 parentPoms.put( parentPom.getCanonicalPath(), parent );
241                 context.setParent( parent.getContext() );
242             }
243 
244             // Map in the parent context which already has the properties loaded
245             integrateMapInContext( parent.getContext().getVariables(), context );
246 
247             project.mergeParent( parent );
248         }
249 
250         project.resolveIds();
251 
252         applyDefaults( context );
253 
254         // Set the created context, and put the project itself in the context. This
255         // is how we get the ${pom} reference in the project.xml file to work.
256         project.setContext( context );
257         context.setProject( project );
258 
259         return project;
260     }
261 
262     /**
263      * This is currently used for the reactor but may be generally useful.
264      * 
265      * @param directory
266      *            the directory to scan for maven projects
267      * @param includes
268      *            the pattern that matches a project that you want included
269      * @param excludes
270      *            the pattern that matches a project that you don't want included
271      * @return a {link List} of {@link Project}s
272      * @throws MavenException
273      *             when anything goes wrong.
274      */
275     public static List getProjects( File directory, String includes, String excludes ) throws MavenException
276     {
277         return getProjects( directory, includes, excludes, null );
278     }
279 
280     /**
281      * This is currently used for the reactor but may be generally useful.
282      * 
283      * @param directory
284      *            the directory to scan for maven projects
285      * @param includes
286      *            Patterns to include.
287      * @param excludes
288      *            Patterns to exclude.
289      * @param context
290      *            the parent context
291      * @return a {link List} of {@link Project}s
292      * @throws MavenException
293      *             when anything goes wrong.
294      */
295     public static List getProjects( File directory, String includes, String excludes, MavenJellyContext context )
296         throws MavenException
297     {
298         String[] files = getFiles( directory, includes, excludes );
299 
300         List projects = new ArrayList();
301 
302         for ( int i = 0; i < files.length; i++ )
303         {
304             Project p = getProject( new File( files[i] ), context );
305             projects.add( p );
306         }
307 
308         return projects;
309     }
310 
311     /**
312      * Take the POM and interpolate the value of the project's context to create a new version of the POM with expanded
313      * context values.
314      * 
315      * @param project
316      *            the maven POM
317      * @return Jelly interpolated project.
318      * @throws Exception
319      *             when there are errors reading FIXME
320      */
321     public static Project getJellyProject( Project project ) throws Exception
322     {
323         // Keep a copy of the original context
324         MavenJellyContext originalContext = project.getContext();
325 
326         // We don't want any taglib references in the context or Jelly
327         // gets confused. All we want are the variables for interpolation. We
328         // can change this but I think we would like to avoid general Jelly
329         // idiom in the POM anyway.
330         JellyContext context = new JellyContext();
331         JellyUtils.populateVariables( context, originalContext );
332 
333         // We don't want the context or the parent being written out into the XML which
334         // is the interpolated POM.
335         project.setContext( null );
336         Project parent = project.getParent();
337         project.setParent( null );
338 
339         // Interpolate
340         project = getInterpolatedPOM( project, context );
341 
342         // Restore parent and context
343         project.setParent( parent );
344         project.setContext( originalContext );
345         project.getContext().setProject( project );
346 
347         return project;
348     }
349 
350     /**
351      * Get the POM with all variables resolved.
352      * 
353      * @param project
354      *            the project to resolve
355      * @param context
356      *            the context to retrieve variables from
357      * @return a project with no unresolved elements.
358      * @throws Exception
359      *             if there is an error parsing the project FIXME
360      */
361     private static Project getInterpolatedPOM( Project project, JellyContext context ) throws Exception
362     {
363         String projectString = project.getProjectAsString();
364         Expression e = JellyUtils.decomposeExpression( projectString, context );
365         String newProjectString = e.evaluateAsString( context );
366         // We can use a Reader and not an URL/path here to read
367         // the POM because this is a memory model without XML entities.
368         project = new Project( new StringReader( newProjectString ) );
369         return project;
370     }
371 
372     /**
373      * Get a set of files from a specifed directory with a set of includes.
374      * 
375      * @param directory
376      *            Directory to scan.
377      * @param includes
378      *            Comma separated list of includes.
379      * @return files
380      */
381     public static String[] getFiles( File directory, String includes )
382     {
383         return getFiles( directory, includes, null );
384     }
385 
386     /**
387      * Get a set of files from a specifed directory with a set of includes.
388      * 
389      * @param directory
390      *            Directory to scan.
391      * @param includes
392      *            Comma separated list of includes.
393      * @param excludes
394      *            Comma separated list of excludes.
395      * @return files
396      */
397     public static String[] getFiles( File directory, String includes, String excludes )
398     {
399         String[] includePatterns = null;
400         if ( includes != null )
401         {
402             includePatterns = StringUtils.split( includes, "," );
403         }
404 
405         String[] excludePatterns = null;
406         if ( excludes != null )
407         {
408             excludePatterns = StringUtils.split( excludes, "," );
409         }
410 
411         DirectoryScanner directoryScanner = new DirectoryScanner();
412         directoryScanner.setBasedir( directory );
413         directoryScanner.setIncludes( includePatterns );
414         directoryScanner.setExcludes( excludePatterns );
415         directoryScanner.scan();
416         String[] files = directoryScanner.getIncludedFiles();
417 
418         for ( int i = 0; i < files.length; i++ )
419         {
420             files[i] = new File( directory, files[i] ).getAbsolutePath();
421         }
422 
423         return files;
424     }
425 
426     /**
427      * Take a dominant and recessive Map and merge the key:value pairs where the recessive Map may add key:value pairs
428      * to the dominant Map but may not override any existing key:value pairs.
429      * 
430      * If we have two Maps, a dominant and recessive, and their respective keys are as follows:
431      * 
432      * dominantMapKeys = { a, b, c, d, e, f } recessiveMapKeys = { a, b, c, x, y, z }
433      * 
434      * Then the result should be the following:
435      * 
436      * resultantKeys = { a, b, c, d, e, f, x, y, z }
437      * 
438      * @param dominantMap
439      *            Dominant Map.
440      * @param recessiveMap
441      *            Recessive Map.
442      * @return The result map with combined dominant and recessive values.
443      */
444     public static Map mergeMaps( Map dominantMap, Map recessiveMap )
445     {
446         Map result = new HashMap();
447 
448         if ( ( dominantMap == null ) && ( recessiveMap == null ) )
449         {
450             return null;
451         }
452 
453         if ( ( dominantMap != null ) && ( recessiveMap == null ) )
454         {
455             return dominantMap;
456         }
457 
458         if ( dominantMap == null )
459         {
460             return recessiveMap;
461         }
462 
463         // Grab the keys from the dominant and recessive maps.
464         Set dominantMapKeys = dominantMap.keySet();
465         Set recessiveMapKeys = recessiveMap.keySet();
466 
467         // Create the set of keys that will be contributed by the
468         // recessive Map by subtracting the intersection of keys
469         // from the recessive Map's keys.
470         Collection contributingRecessiveKeys =
471             CollectionUtils.subtract( recessiveMapKeys,
472                                       CollectionUtils.intersection( dominantMapKeys, recessiveMapKeys ) );
473 
474         result.putAll( dominantMap );
475 
476         // Now take the keys we just found and extract the values from
477         // the recessiveMap and put the key:value pairs into the dominantMap.
478         for ( Iterator i = contributingRecessiveKeys.iterator(); i.hasNext(); )
479         {
480             Object key = i.next();
481             result.put( key, recessiveMap.get( key ) );
482         }
483 
484         return result;
485     }
486 
487     /**
488      * Take a series of <code>Map</code>s and merge them where the ordering of the array from 0..n is the dominant
489      * order.
490      * 
491      * @param maps
492      *            An array of Maps to merge.
493      * @return Map The result Map produced after the merging process.
494      */
495     public static Map mergeMaps( Map[] maps )
496     {
497         Map result;
498 
499         if ( maps.length == 0 )
500         {
501             result = null;
502         }
503         else if ( maps.length == 1 )
504         {
505             result = maps[0];
506         }
507         else
508         {
509             result = mergeMaps( maps[0], maps[1] );
510 
511             for ( int i = 2; i < maps.length; i++ )
512             {
513                 result = mergeMaps( result, maps[i] );
514             }
515         }
516 
517         return result;
518     }
519 
520     /**
521      * Load the build.properties file for a project.
522      * 
523      * @param directory
524      *            the directory of the project
525      * @return the properties
526      */
527     private static Properties loadProjectBuildProperties( File directory )
528     {
529         // project build properties
530         File projectBuildPropertiesFile = new File( directory, "build.properties" );
531 
532         LOGGER.debug( "Using projectBuildPropertiesFile: " + projectBuildPropertiesFile.getAbsolutePath() );
533         return loadProperties( projectBuildPropertiesFile );
534     }
535 
536     /**
537      * Load the project.properties file for a project.
538      * 
539      * @param directory
540      *            the directory of the project
541      * @return the properties
542      */
543     private static Properties loadProjectProperties( File directory )
544     {
545         // project properties
546         File projectPropertiesFile = new File( directory, "project.properties" );
547 
548         LOGGER.debug( "Using projectPropertiesFile: " + projectPropertiesFile.getAbsolutePath() );
549         return loadProperties( projectPropertiesFile );
550     }
551 
552     /**
553      * Create a jelly context given a descriptor directory.
554      * 
555      * @param descriptorDirectory
556      *            The directory from which to pull the standard maven properties files from.
557      * @return The generated maven based on the contents of the standard maven properties files.
558      */
559     public static MavenJellyContext createContext( File descriptorDirectory )
560     {
561         return createContext( descriptorDirectory, null );
562     }
563 
564     /**
565      * Create a jelly context given a descriptor directory and parent jelly context.
566      * 
567      * @param descriptorDirectory
568      *            The directory from which to pull the standard maven properties files from.
569      * @param parentContext
570      *            The parent jelly context.
571      * @todo should premerge driver, etc if they are being kept
572      * @return The generated maven based on the contents of the standard maven properties files.
573      */
574     public static MavenJellyContext createContext( File descriptorDirectory, MavenJellyContext parentContext )
575     {
576         MavenJellyContext context = createContextNoDefaults( descriptorDirectory, parentContext );
577         applyDefaults( context );
578         return context;
579     }
580 
581     /**
582      * Create a jelly context given a descriptor directory and parent jelly context, but don't apply any defaults.
583      * 
584      * @param descriptorDirectory
585      *            The directory from which to pull the standard maven properties files from.
586      * @param parentContext
587      *            The parent jelly context.
588      * @todo should premerge driver, etc if they are being kept
589      * @return The generated maven based on the contents of the standard maven properties files.
590      */
591     private static MavenJellyContext createContextNoDefaults( File descriptorDirectory, MavenJellyContext parentContext )
592     {
593         // System properties
594         Properties systemProperties = System.getProperties();
595 
596         // User build properties
597         File userBuildPropertiesFile = new File( System.getProperty( "user.home" ), "build.properties" );
598 
599         LOGGER.debug( "Using userBuildPropertiesFile: " + userBuildPropertiesFile.getAbsolutePath() );
600         Properties userBuildProperties = loadProperties( userBuildPropertiesFile );
601 
602         Properties projectProperties = loadProjectProperties( descriptorDirectory );
603         Properties projectBuildProperties = loadProjectBuildProperties( descriptorDirectory );
604 
605         Properties driverProperties =
606             loadProperties( MavenUtils.class.getResourceAsStream( MavenConstants.DRIVER_PROPERTIES ) );
607 
608         Map result =
609             MavenUtils.mergeMaps( new Map[] { systemProperties, userBuildProperties, projectBuildProperties,
610                 projectProperties, driverProperties } );
611 
612         MavenJellyContext context;
613 
614         if ( parentContext != null )
615         {
616             context = new MavenJellyContext( parentContext );
617         }
618         else
619         {
620             context = new MavenJellyContext();
621         }
622 
623         // Turn off inheritence so parent values are overriden
624         context.setInherit( false );
625 
626         // integrate everything else...
627         MavenUtils.integrateMapInContext( result, context );
628 
629         // Turn inheritance back on to make the parent's values visible.
630         context.setInherit( true );
631 
632         // Set the basedir value in the context.
633         context.setVariable( "basedir", descriptorDirectory.getAbsolutePath() );
634 
635         return context;
636     }
637 
638     /**
639      * Apply default settings.
640      * 
641      * @param context
642      *            Jelly context to apply the defaults.
643      */
644     private static void applyDefaults( MavenJellyContext context )
645     {
646         Properties defaultProperties =
647             loadProperties( MavenUtils.class.getResourceAsStream( MavenConstants.DEFAULTS_PROPERTIES ) );
648 
649         // integrate defaults...
650         MavenUtils.integrateMapInContext( defaultProperties, context );
651 
652         // deliberately use the original base directory for these variables
653         context.resolveRelativePaths( new File( System.getProperty( "user.dir" ) ) );
654     }
655 
656     /**
657      * Integrate a Map of key:value pairs into a <code>MavenJellyContext</code>. The values in the Map may be
658      * <code>CompositeExpression</code>s that need to be evaluated before being placed into the context.
659      * 
660      * @param map
661      *            Map to integrate into the provided jelly context.
662      * @param context
663      *            Jelly context to integrate the map into.
664      */
665     public static void integrateMapInContext( Map map, MavenJellyContext context )
666     {
667         if ( map == null )
668         {
669             return;
670         }
671 
672         JexlExpressionFactory factory = new JexlExpressionFactory();
673 
674         for ( Iterator i = map.keySet().iterator(); i.hasNext(); )
675         {
676             String key = (String) i.next();
677             Object value;
678 
679             // Parent contexts are already handled, so only concern ourselves with whether it exists in the current
680             // context
681             if ( context.getVariables().get( key ) == null )
682             {
683                 value = map.get( key );
684 
685                 if ( value instanceof String )
686                 {
687                     try
688                     {
689                         String literalValue = (String) value;
690                         Expression expr = CompositeExpression.parse( literalValue, factory );
691 
692                         if ( expr != null )
693                         {
694                             value = expr;
695                         }
696                         else
697                         {
698                             value = literalValue;
699                         }
700                     }
701                     catch ( Exception e )
702                     {
703                         // do nothing.
704                         LOGGER.debug( "Unexpected error evaluating expression", e );
705                     }
706                 }
707                 context.setVariable( key, value );
708             }
709         }
710     }
711 
712     /**
713      * Load properties from a <code>File</code>.
714      * 
715      * @param file
716      *            Propertie file to load.
717      * @return The loaded Properties.
718      */
719     private static Properties loadProperties( File file )
720     {
721         FileInputStream fis = null;
722         try
723         {
724             if ( file.exists() )
725             {
726                 fis = new FileInputStream( file );
727                 return loadProperties( fis );
728             }
729         }
730         catch ( Exception e )
731         {
732             // ignore
733             LOGGER.debug( "Unexpected error loading properties", e );
734         }
735         finally
736         {
737             if ( fis != null )
738             {
739                 try
740                 {
741                     fis.close();
742                 }
743                 catch ( IOException e )
744                 {
745                     LOGGER.debug( "WARNING: Cannot close stream!", e );
746                 }
747                 fis = null;
748             }
749         }
750 
751         return null;
752     }
753 
754     /**
755      * Load properties from an <code>InputStream</code>.
756      * 
757      * @param is
758      *            InputStream from which load properties.
759      * @return The loaded Properties.
760      */
761     private static Properties loadProperties( InputStream is )
762     {
763         try
764         {
765             Properties properties = new Properties();
766             properties.load( is );
767 
768             for ( Iterator i = properties.keySet().iterator(); i.hasNext(); )
769             {
770                 String property = (String) i.next();
771                 properties.setProperty( property, properties.getProperty( property ).trim() );
772             }
773 
774             return properties;
775         }
776         catch ( IOException e )
777         {
778             // ignore
779             LOGGER.debug( "Unexpected exception loading properties", e );
780         }
781         finally
782         {
783             try
784             {
785                 if ( is != null )
786                 {
787                     is.close();
788                 }
789             }
790             catch ( IOException e )
791             {
792                 // ignore
793                 LOGGER.debug( "Unexpected exception loading properties", e );
794             }
795         }
796 
797         return null;
798     }
799 
800     /** Resource bundle with user messages. */
801     private static ResourceBundle messages;
802 
803     /**
804      * Load MavenSession user messages from a resource bundle given the user's locale.
805      * 
806      * @todo Move locale tools into their own class.
807      */
808     private static void loadMessages()
809     {
810         try
811         {
812             // Look for the message bundle corresponding to the user's locale.
813             messages = ResourceBundle.getBundle( "org/apache/maven/messages/messages" );
814         }
815         catch ( MissingResourceException e )
816         {
817             // If we can't find the appropriate message bundle for the locale then
818             // we will fall back to English.
819             messages = ResourceBundle.getBundle( "org/apache/maven/messages/messages", Locale.ENGLISH );
820         }
821     }
822 
823     /**
824      * Retrieve a user message.
825      * 
826      * @param messageId
827      *            Id of message type to use.
828      * @return Message for the user's locale.
829      */
830     public static String getMessage( String messageId )
831     {
832         return getMessage( messageId, null );
833     }
834 
835     /**
836      * Retrieve a user message.
837      * 
838      * @param messageId
839      *            Id of message type to use.
840      * @param variable
841      *            Value to substitute for ${1} in the given message.
842      * @return Message for the user's locale.
843      */
844     public static String getMessage( String messageId, Object variable )
845     {
846         if ( messages == null )
847         {
848             try
849             {
850                 loadMessages();
851             }
852             catch ( MissingResourceException mre )
853             {
854                 LOGGER.error( mre );
855                 return messageId + ( variable == null ? "" : " " + variable );
856             }
857         }
858 
859         if ( variable == null )
860         {
861             return messages.getString( messageId );
862         }
863         else
864         {
865             return StringUtils.replace( messages.getString( messageId ), "${1}", variable.toString() );
866         }
867     }
868 
869     /**
870      * Resolve directory against a base directory if it is not already absolute.
871      * 
872      * @param basedir
873      *            the base directory for relative paths
874      * @param dir
875      *            the directory to resolve
876      * @throws IOException
877      *             if canonical path fails
878      * @return the canonical path of the directory if not absolute
879      */
880     public static String makeAbsolutePath( File basedir, String dir ) throws IOException
881     {
882         File f = new File( dir );
883         if ( ( f.isAbsolute() ) )
884         {
885             return f.getCanonicalPath();
886         }
887         else
888         {
889             return new File( basedir, dir ).getCanonicalPath();
890         }
891     }
892 
893     /**
894      * Convert an absolute path to a relative path if it is under a given base directory.
895      * 
896      * @param basedir
897      *            the base directory for relative paths
898      * @param path
899      *            the directory to resolve
900      * @return the relative path
901      * @throws IOException
902      *             if canonical path fails
903      */
904     public static String makeRelativePath( File basedir, String path ) throws IOException
905     {
906         String canonicalBasedir = basedir.getCanonicalPath();
907         File pathFile = new File( path );
908         if ( !pathFile.isAbsolute() )
909         {
910             LOGGER.warn( "WARNING: path is not an absolute pathname! Returning path." );
911             return path;
912         }
913         String canonicalPath = pathFile.getCanonicalPath();
914 
915         if ( canonicalPath.equals( canonicalBasedir ) )
916         {
917             return ".";
918         }
919 
920         if ( canonicalPath.startsWith( canonicalBasedir ) )
921         {
922             if ( canonicalPath.charAt( canonicalBasedir.length() ) == File.separatorChar )
923             {
924                 canonicalPath = canonicalPath.substring( canonicalBasedir.length() + 1 );
925             }
926             else
927             {
928                 canonicalPath = canonicalPath.substring( canonicalBasedir.length() );
929             }
930         }
931         else
932         {
933             LOGGER.warn( "WARNING: path does not start with basedir! Returning path." );
934             return path;
935         }
936         return canonicalPath;
937     }
938 
939     /**
940      * Get a list of goals from a CSV list.
941      * 
942      * @param goalCsv
943      *            the goals
944      * @return the list of goal names
945      */
946     public static List getGoalListFromCsv( String goalCsv )
947     {
948         StringTokenizer tok = new StringTokenizer( goalCsv, "," );
949         List goals = new ArrayList();
950         while ( tok.hasMoreTokens() )
951         {
952             goals.add( tok.nextToken().trim() );
953         }
954         return goals;
955     }
956 
957     /**
958      * Debugging function.
959      * 
960      * @param classLoader
961      *            the class loader
962      */
963     public static void displayClassLoaderContents( ForeheadClassLoader classLoader )
964     {
965         LOGGER.info( "ClassLoader name: " + classLoader.getName() );
966 
967         URL[] urls = classLoader.getURLs();
968 
969         for ( int i = 0; i < urls.length; i++ )
970         {
971             LOGGER.info( "urls[" + i + "] = " + urls[i] );
972         }
973 
974         ClassLoader parent = classLoader.getParent();
975         if ( ( parent != null ) && ( parent instanceof ForeheadClassLoader ) )
976         {
977             LOGGER.info( "Displaying Parent classloader: " );
978             displayClassLoaderContents( (ForeheadClassLoader) classLoader.getParent() );
979         }
980     }
981 
982 
983 }