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