View Javadoc

1   package org.apache.maven.util;
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.codehaus.plexus.util.StringUtils;
22  
23  /**
24   * Path tool for use with the DVSL toolbox.  This class contains static
25   * methods to assist in determining path-related information such as
26   * relative paths.
27   *
28   * @author <a href="mailto:pete-apache-dev@kazmier.com">Pete Kazmier</a>
29   * @author <a href="mailto:vmassol@apache.org">Vincent Massol</a>
30   * @version $Id: DVSLPathTool.java 517014 2007-03-11 21:15:50Z ltheussl $
31   * @todo move to org.apache.maven.util or make a jelly tag
32   */
33  public class DVSLPathTool
34  {
35      /**
36       * Determines the relative path of a filename from a base directory.
37       * This method is useful in building relative links within pages of
38       * a web site.  It provides similar functionality to Anakia's
39       * <code>$relativePath</code> context variable.  The arguments to
40       * this method may contain either forward or backward slashes as
41       * file separators.  The relative path returned is formed using
42       * forward slashes as it is expected this path is to be used as a
43       * link in a web page (again mimicking Anakia's behavior).
44       * <p/>
45       * This method is thread-safe.
46       *
47       * @param basedir The base directory.
48       * @param filename The filename that is relative to the base
49       * directory.
50       * @return The relative path of the filename from the base
51       * directory.  This value is not terminated with a forward slash.
52       * A zero-length string is returned if: the filename is not relative to
53       * the base directory, <code>basedir</code> is null or zero-length,
54       * or <code>filename</code> is null or zero-length.
55       */
56      public static final String getRelativePath( String basedir, String filename )
57      {
58          basedir = uppercaseDrive( basedir );
59          filename = uppercaseDrive( filename );
60  
61          /*
62           * Verify the arguments and make sure the filename is relative
63           * to the base directory.
64           */
65          if ( ( basedir == null ) || ( basedir.length() == 0 ) || ( filename == null ) || ( filename.length() == 0 )
66              || !filename.startsWith( basedir ) )
67          {
68              return "";
69          }
70  
71          /*
72           * Normalize the arguments.  First, determine the file separator
73           * that is being used, then strip that off the end of both the
74           * base directory and filename.
75           */
76          String separator = determineSeparator( filename );
77          basedir = StringUtils.chompLast( basedir, separator );
78          filename = StringUtils.chompLast( filename, separator );
79  
80          /*
81           * Remove the base directory from the filename to end up with a
82           * relative filename (relative to the base directory).  This
83           * filename is then used to determine the relative path.
84           */
85          String relativeFilename = filename.substring( basedir.length() );
86  
87          return determineRelativePath( relativeFilename, separator );
88      }
89  
90      /**
91       * Determines the relative path of a filename.  This method is
92       * useful in building relative links within pages of a web site.  It
93       * provides similar functionality to Anakia's
94       * <code>$relativePath</code> context variable.  The argument to
95       * this method may contain either forward or backward slashes as
96       * file separators.  The relative path returned is formed using
97       * forward slashes as it is expected this path is to be used as a
98       * link in a web page (again mimicking Anakia's behavior).
99       * <p/>
100      * This method is thread-safe.
101      *
102      * @param filename The filename to be parsed.
103      * @return The relative path of the filename. This value is not
104      * terminated with a forward slash.  A zero-length string is
105      * returned if: <code>filename</code> is null or zero-length.
106      */
107     public static final String getRelativePath( String filename )
108     {
109         filename = uppercaseDrive( filename );
110 
111         if ( ( filename == null ) || ( filename.length() == 0 ) )
112         {
113             return "";
114         }
115 
116         /*
117          * Normalize the argument.  First, determine the file separator
118          * that is being used, then strip that off the end of the
119          * filename.  Then, if the filename doesn't begin with a
120          * separator, add one.
121          */
122 
123         String separator = determineSeparator( filename );
124         filename = StringUtils.chompLast( filename, separator );
125         if ( !filename.startsWith( separator ) )
126         {
127             filename = separator + filename;
128         }
129 
130         return determineRelativePath( filename, separator );
131     }
132 
133     /**
134      * Determines the directory component of a filename.  This is useful
135      * within DVSL templates when used in conjunction with the DVSL's
136      * <code>$context.getAppValue("infilename")</code> to get the
137      * current directory that is currently being processed.
138      * <p/>
139      * This method is thread-safe.
140      *
141      * @param filename The filename to be parsed.
142      * @return The directory portion of the <code>filename</code>.  If
143      * the filename does not contain a directory component, "." is
144      * returned.
145      */
146     public static final String getDirectoryComponent( String filename )
147     {
148         if ( ( filename == null ) || ( filename.length() == 0 ) )
149         {
150             return "";
151         }
152 
153         String separator = determineSeparator( filename );
154         String directory = StringUtils.chomp( filename, separator );
155 
156         if ( filename.equals( directory ) )
157         {
158             return ".";
159         }
160         else
161         {
162             return directory;
163         }
164     }
165 
166     /**
167      * Determines the relative path of a filename.  For each separator
168      * within the filename (except the leading if present), append the
169      * "../" string to the return value.
170      *
171      * @param filename The filename to parse.
172      * @param separator The separator used within the filename.
173      * @return The relative path of the filename.  This value is not
174      * terminated with a forward slash.  A zero-length string is
175      * returned if: the filename is zero-length.
176      */
177     private static final String determineRelativePath( String filename, String separator )
178     {
179         if ( filename.length() == 0 )
180         {
181             return "";
182         }
183 
184         /*
185          * Count the slashes in the relative filename, but exclude the
186          * leading slash.  If the path has no slashes, then the filename
187          * is relative to the current directory.
188          */
189         int slashCount = StringUtils.countMatches( filename, separator ) - 1;
190         if ( slashCount <= 0 )
191         {
192             return ".";
193         }
194 
195         /*
196          * The relative filename contains one or more slashes indicating
197          * that the file is within one or more directories.  Thus, each
198          * slash represents a "../" in the relative path.
199          */
200         StringBuffer sb = new StringBuffer();
201         for ( int i = 0; i < slashCount; i++ )
202         {
203             sb.append( "../" );
204         }
205 
206         /*
207          * Finally, return the relative path but strip the trailing
208          * slash to mimic Anakia's behavior.
209          */
210         return StringUtils.chop( sb.toString() );
211     }
212 
213     /**
214      * Helper method to determine the file separator (forward or
215      * backward slash) used in a filename.  The slash that occurs more
216      * often is returned as the separator.
217      *
218      * @param filename The filename parsed to determine the file
219      * separator.
220      * @return The file separator used within <code>filename</code>.
221      * This value is either a forward or backward slash.
222      */
223     private static final String determineSeparator( String filename )
224     {
225         int forwardCount = StringUtils.countMatches( filename, "/" );
226         int backwardCount = StringUtils.countMatches( filename, "\\" );
227 
228         return forwardCount >= backwardCount ? "/" : "\\";
229     }
230 
231     /**
232      * Cygwin prefers lowercase drive letters, but other parts of maven use uppercase
233      * @param path
234      * @return String
235      */
236     static final String uppercaseDrive( String path )
237     {
238         if ( path == null )
239         {
240             return null;
241         }
242         if ( ( path.length() >= 2 ) && ( path.charAt( 1 ) == ':' ) )
243         {
244             path = path.substring( 0, 1 ).toUpperCase() + path.substring( 1 );
245         }
246         return path;
247     }
248 
249     /**
250      * Calculates the appropriate link given the preferred link and the relativePath of the document
251      * @param link
252      * @param relativePath
253      * @return String
254      */
255     public static final String calculateLink( String link, String relativePath )
256     {
257         //This must be some historical feature
258         if ( link.startsWith( "/site/" ) )
259         {
260             return link.substring( 5 );
261         }
262 
263         //Allows absolute links in nav-bars etc
264         if ( link.startsWith( "/absolute/" ) )
265         {
266             return link.substring( 10 );
267         }
268 
269         // This traps urls like http://
270         if ( link.indexOf( ":" ) >= 0 )
271         {
272             return link;
273         }
274 
275         //If relativepath is current directory, just pass the link through
276         if ( ".".equals( relativePath ) )
277         {
278             if ( link.startsWith( "/" ) )
279             {
280                 return link.substring( 1 );
281             }
282             else
283             {
284                 return link;
285             }
286         }
287 
288         //If we don't do this, you can end up with ..//bob.html rather than ../bob.html
289         if ( relativePath.endsWith( "/" ) && link.startsWith( "/" ) )
290         {
291             return relativePath + "." + link.substring( 1 );
292         }
293 
294         if ( relativePath.endsWith( "/" ) || link.startsWith( "/" ) )
295         {
296             return relativePath + link;
297         }
298 
299         return relativePath + "/" + link;
300     }
301 
302 }