View Javadoc
1   package org.apache.maven.shared.release.util;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.File;
23  import java.io.IOException;
24  import java.io.Reader;
25  import java.util.Arrays;
26  import java.util.List;
27  import java.util.Locale;
28  
29  import org.apache.commons.lang.StringUtils;
30  import org.apache.maven.model.Model;
31  import org.apache.maven.project.MavenProject;
32  import org.apache.maven.shared.release.ReleaseExecutionException;
33  import org.apache.maven.shared.release.config.ReleaseDescriptor;
34  import org.codehaus.plexus.interpolation.InterpolationException;
35  import org.codehaus.plexus.interpolation.MapBasedValueSource;
36  import org.codehaus.plexus.interpolation.ObjectBasedValueSource;
37  import org.codehaus.plexus.interpolation.PrefixAwareRecursionInterceptor;
38  import org.codehaus.plexus.interpolation.PrefixedObjectValueSource;
39  import org.codehaus.plexus.interpolation.StringSearchInterpolator;
40  import org.codehaus.plexus.util.FileUtils;
41  import org.codehaus.plexus.util.IOUtil;
42  import org.codehaus.plexus.util.ReaderFactory;
43  
44  /**
45   * @author <a href="mailto:evenisse@apache.org">Emmanuel Venisse</a>
46   * @version $Id: ReleaseUtil.java 1418704 2012-12-08 16:43:26Z rfscholte $
47   */
48  public class ReleaseUtil
49  {
50      public static final String RELEASE_POMv4 = "release-pom.xml";
51  
52      public static final String POMv4 = "pom.xml";
53  
54      private static final String FS = File.separator;
55  
56      /**
57       * The line separator to use.
58       */
59      public static final String LS = System.getProperty( "line.separator" );
60  
61      private ReleaseUtil()
62      {
63          // noop
64      }
65  
66      public static MavenProject getRootProject( List<MavenProject> reactorProjects )
67      {
68          MavenProject project = reactorProjects.get( 0 );
69          for ( MavenProject currentProject : reactorProjects )
70          {
71              if ( currentProject.isExecutionRoot() )
72              {
73                  project = currentProject;
74                  break;
75              }
76          }
77  
78          return project;
79      }
80  
81      public static File getStandardPom( MavenProject project )
82      {
83          if ( project == null )
84          {
85              return null;
86          }
87  
88          File pom = project.getFile();
89  
90          if ( pom == null )
91          {
92              return null;
93          }
94  
95          File releasePom = getReleasePom( project );
96          if ( pom.equals( releasePom ) )
97          {
98              pom = new File( pom.getParent(), POMv4 );
99          }
100 
101         return pom;
102     }
103 
104     public static File getReleasePom( MavenProject project )
105     {
106         if ( project == null )
107         {
108             return null;
109         }
110 
111         File pom = project.getFile();
112 
113         if ( pom == null )
114         {
115             return null;
116         }
117 
118         return new File( pom.getParent(), RELEASE_POMv4 );
119     }
120 
121     /**
122      * Gets the string contents of the specified XML file. Note: In contrast to an XML processor, the line separators in
123      * the returned string will be normalized to use the platform's native line separator. This is basically to save
124      * another normalization step when writing the string contents back to an XML file.
125      * 
126      * @param file The path to the XML file to read in, must not be <code>null</code>.
127      * @return The string contents of the XML file.
128      * @throws IOException If the file could not be opened/read.
129      */
130     public static String readXmlFile( File file )
131         throws IOException
132     {
133         return readXmlFile( file, LS );
134     }
135 
136     public static String readXmlFile( File file, String ls )
137         throws IOException
138     {
139         Reader reader = null;
140         try
141         {
142             reader = ReaderFactory.newXmlReader( file );
143             return normalizeLineEndings( IOUtil.toString( reader ), ls );
144         }
145         finally
146         {
147             IOUtil.close( reader );
148         }
149     }
150 
151     /**
152      * Normalizes the line separators in the specified string.
153      * 
154      * @param text The string to normalize, may be <code>null</code>.
155      * @param separator The line separator to use for normalization, typically "\n" or "\r\n", must not be
156      *            <code>null</code>.
157      * @return The input string with normalized line separators or <code>null</code> if the string was <code>null</code>
158      *         .
159      */
160     public static String normalizeLineEndings( String text, String separator )
161     {
162         String norm = text;
163         if ( text != null )
164         {
165             norm = text.replaceAll( "(\r\n)|(\n)|(\r)", separator );
166         }
167         return norm;
168     }
169 
170     public static ReleaseDescriptor createBasedirAlignedReleaseDescriptor( ReleaseDescriptor releaseDescriptor,
171                                                                            List<MavenProject> reactorProjects )
172         throws ReleaseExecutionException
173     {
174         String basedir;
175         try
176         {
177             basedir = getCommonBasedir( reactorProjects );
178         }
179         catch ( IOException e )
180         {
181             throw new ReleaseExecutionException( "Exception occurred while calculating common basedir: "
182                 + e.getMessage(), e );
183         }
184 
185         int parentLevels =
186             getBaseWorkingDirectoryParentCount( basedir, FileUtils.normalize( releaseDescriptor.getWorkingDirectory() ) );
187 
188         String url = releaseDescriptor.getScmSourceUrl();
189         url = realignScmUrl( parentLevels, url );
190 
191         ReleaseDescriptor descriptor = new ReleaseDescriptor();
192         descriptor.setWorkingDirectory( basedir );
193         descriptor.setScmSourceUrl( url );
194         return descriptor;
195     }
196 
197     public static String getCommonBasedir( List<MavenProject> reactorProjects )
198         throws IOException
199     {
200         return getCommonBasedir( reactorProjects, FS );
201     }
202 
203     public static String getCommonBasedir( List<MavenProject> reactorProjects, String separator )
204         throws IOException
205     {
206         String[] baseDirs = new String[reactorProjects.size()];
207         int idx = 0;
208         for ( MavenProject p : reactorProjects )
209         {
210             String dir = p.getBasedir().getCanonicalPath();
211 
212             // always end with separator so that we know what is a path and what is a partial directory name in the
213             // next call
214             if ( !dir.endsWith( separator ) )
215             {
216                 dir = dir + separator;
217             }
218             baseDirs[idx++] = dir;
219         }
220 
221         String basedir = StringUtils.getCommonPrefix( baseDirs );
222 
223         int separatorPos = basedir.lastIndexOf( separator );
224         if ( !basedir.endsWith( separator ) && separatorPos >= 0 )
225         {
226             basedir = basedir.substring( 0, separatorPos );
227         }
228 
229         if ( basedir.endsWith( separator ) && basedir.length() > 1 )
230         {
231             basedir = basedir.substring( 0, basedir.length() - 1 );
232         }
233 
234         return basedir;
235     }
236 
237     public static int getBaseWorkingDirectoryParentCount( String basedir, String workingDirectory )
238     {
239         int num = 0;
240 
241         // we can safely assume case-insensitivity as we are just backtracking, not comparing. This helps with issues
242         // on Windows with C: vs c:
243         workingDirectory = workingDirectory.toLowerCase( Locale.ENGLISH );
244         basedir = basedir.toLowerCase( Locale.ENGLISH );
245 
246         // MRELEASE-663
247         // For Windows is does matter if basedir ends with a file-separator or not to be able to compare.
248         // Using the parent of a dummy file makes it possible to compare them OS-independent
249         File workingDirectoryFile = new File( workingDirectory, ".tmp" ).getParentFile();
250         File basedirFile = new File( basedir, ".tmp" ).getParentFile();
251 
252         if ( !workingDirectoryFile.equals( basedirFile ) && workingDirectory.startsWith( basedir ) )
253         {
254             do
255             {
256                 workingDirectoryFile = workingDirectoryFile.getParentFile();
257                 num++;
258             }
259             while ( !workingDirectoryFile.equals( basedirFile ) );
260         }
261         return num;
262     }
263 
264     public static String realignScmUrl( int parentLevels, String url )
265     {
266         if ( !StringUtils.isEmpty( url ) )
267         {
268             int index = url.length();
269             String suffix = "";
270             if ( url.endsWith( "/" ) )
271             {
272                 index--;
273                 suffix = "/";
274             }
275             for ( int i = 0; i < parentLevels && index > 0; i++ )
276             {
277                 index = url.lastIndexOf( '/', index - 1 );
278             }
279 
280             if ( index > 0 )
281             {
282                 url = url.substring( 0, index ) + suffix;
283             }
284         }
285         return url;
286     }
287 
288     public static boolean isSymlink( File file )
289         throws IOException
290     {
291         return !file.getAbsolutePath().equals( file.getCanonicalPath() );
292     }
293 
294     public static String interpolate( String value, Model model )
295         throws ReleaseExecutionException
296     {
297         if ( value != null && value.contains( "${" ) )
298         {
299             StringSearchInterpolator interpolator = new StringSearchInterpolator();
300             List<String> pomPrefixes = Arrays.asList( "pom.", "project." );
301             interpolator.addValueSource( new PrefixedObjectValueSource( pomPrefixes, model, false ) );
302             interpolator.addValueSource( new MapBasedValueSource( model.getProperties() ) );
303             interpolator.addValueSource( new ObjectBasedValueSource( model ) );
304             try
305             {
306                 value = interpolator.interpolate( value, new PrefixAwareRecursionInterceptor( pomPrefixes ) );
307             }
308             catch ( InterpolationException e )
309             {
310                 throw new ReleaseExecutionException(
311                                                      "Failed to interpolate " + value + " for project " + model.getId(),
312                                                      e );
313             }
314         }
315         return value;
316     }
317 }