View Javadoc
1   /*
2    * SmartSprites Project
3    *
4    * Copyright (C) 2007-2009, Stanisław Osiński.
5    * All rights reserved.
6    *
7    * Redistribution and use in source and binary forms, with or without modification,
8    * are permitted provided that the following conditions are met:
9    *
10   * - Redistributions of  source code must  retain the above  copyright notice, this
11   *   list of conditions and the following disclaimer.
12   *
13   * - Redistributions in binary form must reproduce the above copyright notice, this
14   *   list of conditions and the following  disclaimer in  the documentation  and/or
15   *   other materials provided with the distribution.
16   *
17   * - Neither the name of the SmartSprites Project nor the names of its contributors
18   *   may  be used  to endorse  or  promote  products derived   from  this  software
19   *   without specific prior written permission.
20   *
21   * - We kindly request that you include in the end-user documentation provided with
22   *   the redistribution and/or in the software itself an acknowledgement equivalent
23   *   to  the  following: "This product includes software developed by the SmartSprites
24   *   Project."
25   *
26   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"  AND
27   * ANY EXPRESS OR  IMPLIED WARRANTIES, INCLUDING,  BUT NOT LIMITED  TO, THE IMPLIED
28   * WARRANTIES  OF  MERCHANTABILITY  AND  FITNESS  FOR  A  PARTICULAR  PURPOSE   ARE
29   * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE  FOR
30   * ANY DIRECT, INDIRECT, INCIDENTAL,  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL  DAMAGES
31   * (INCLUDING, BUT  NOT LIMITED  TO, PROCUREMENT  OF SUBSTITUTE  GOODS OR SERVICES;
32   * LOSS OF USE, DATA, OR PROFITS;  OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND  ON
33   * ANY  THEORY  OF  LIABILITY,  WHETHER  IN  CONTRACT,  STRICT  LIABILITY,  OR TORT
34   * (INCLUDING NEGLIGENCE OR OTHERWISE)  ARISING IN ANY WAY  OUT OF THE USE  OF THIS
35   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36   */
37  package org.carrot2.util;
38  
39  import com.google.common.base.Strings;
40  
41  import java.io.File;
42  import java.util.StringTokenizer;
43  
44  /**
45   * This class defines utilities methods helping to determine path-related information such as relative paths.
46   * <p>
47   * The original code comes from <b>org.codehaus.plexus.util.PathTool</b>.
48   *
49   * @author Ibrahim Chaehoi
50   * @author <a href="mailto:pete-apache-dev@kazmier.com">Pete Kazmier</a>
51   * @author <a href="mailto:vmassol@apache.org">Vincent Massol</a>
52   * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a>
53   */
54  public class PathUtils {
55  
56      /**
57       * Instantiates a new path utils.
58       */
59      private PathUtils() {
60          // Prevent Instantiation
61      }
62  
63      /**
64       * This method can calculate the relative path between two pathes on a file system.
65       *
66       * <pre>
67       * PathUtils.getRelativeFilePath( null, null )                                   = ""
68       * PathUtils.getRelativeFilePath( null, "/usr/local/java/bin" )                  = ""
69       * PathUtils.getRelativeFilePath( "/usr/local", null )                           = ""
70       * PathUtils.getRelativeFilePath( "/usr/local", "/usr/local/java/bin" )          = "java/bin"
71       * PathUtils.getRelativeFilePath( "/usr/local", "/usr/local/java/bin/" )         = "java/bin"
72       * PathUtils.getRelativeFilePath( "/usr/local/java/bin", "/usr/local/" )         = "../.."
73       * PathUtils.getRelativeFilePath( "/usr/local/", "/usr/local/java/bin/java.sh" ) = "java/bin/java.sh"
74       * PathUtils.getRelativeFilePath( "/usr/local/java/bin/java.sh", "/usr/local/" ) = "../../.."
75       * PathUtils.getRelativeFilePath( "/usr/local/", "/bin" )                        = "../../bin"
76       * PathUtils.getRelativeFilePath( "/bin", "/usr/local/" )                        = "../usr/local"
77       * </pre>
78       *
79       * Note: On Windows based system, the <code>/</code> character should be replaced by <code>\</code> character.
80       *
81       * @param oldPath
82       *            the old path
83       * @param newPath
84       *            the new path
85       *
86       * @return a relative file path from <code>oldPath</code>.
87       */
88      public static final String getRelativeFilePath(final String oldPath, final String newPath) {
89          if (Strings.isNullOrEmpty(oldPath) || Strings.isNullOrEmpty(newPath)) {
90              return "";
91          }
92  
93          // normalise the path delimiters
94          String fromPath = new File(oldPath).getPath();
95          String toPath = new File(newPath).getPath();
96  
97          // strip any leading slashes if its a windows path
98          if (toPath.matches("^\\[a-zA-Z]:")) {
99              toPath = toPath.substring(1);
100         }
101         if (fromPath.matches("^\\[a-zA-Z]:")) {
102             fromPath = fromPath.substring(1);
103         }
104 
105         // lowercase windows drive letters.
106         if (fromPath.startsWith(":", 1)) {
107             fromPath = Character.toLowerCase(fromPath.charAt(0)) + fromPath.substring(1);
108         }
109         if (toPath.startsWith(":", 1)) {
110             toPath = Character.toLowerCase(toPath.charAt(0)) + toPath.substring(1);
111         }
112 
113         // check for the presence of windows drives. No relative way of
114         // traversing from one to the other.
115         if (toPath.startsWith(":", 1) && fromPath.startsWith(":", 1)
116                 && !toPath.substring(0, 1).equals(fromPath.substring(0, 1))) {
117             // they both have drive path element but they dont match, no relative path
118             return null;
119         }
120 
121         if ((toPath.startsWith(":", 1) && !fromPath.startsWith(":", 1))
122                 || (!toPath.startsWith(":", 1) && fromPath.startsWith(":", 1))) {
123             // one has a drive path element and the other doesn't, no relative path.
124             return null;
125         }
126 
127         String resultPath = buildRelativePath(toPath, fromPath, File.separatorChar);
128 
129         if (newPath.endsWith(File.separator) && !resultPath.endsWith(File.separator)) {
130             return resultPath + File.separator;
131         }
132 
133         return resultPath;
134     }
135 
136     // ----------------------------------------------------------------------
137     // Private methods
138     // ----------------------------------------------------------------------
139 
140     /**
141      * Builds the relative path.
142      *
143      * @param toPath
144      *            the to path
145      * @param fromPath
146      *            the from path
147      * @param separatorChar
148      *            the separator char
149      *
150      * @return the string
151      */
152     private static String buildRelativePath(String toPath, String fromPath, final char separatorChar) {
153         // use tokenizer to traverse paths and for lazy checking
154         StringTokenizer toTokenizer = new StringTokenizer(toPath, String.valueOf(separatorChar));
155         StringTokenizer fromTokenizer = new StringTokenizer(fromPath, String.valueOf(separatorChar));
156 
157         int count = 0;
158 
159         // walk along the to path looking for divergence from the from path
160         while (toTokenizer.hasMoreTokens() && fromTokenizer.hasMoreTokens()) {
161             if (separatorChar == '\\') {
162                 if (!fromTokenizer.nextToken().equalsIgnoreCase(toTokenizer.nextToken())) {
163                     break;
164                 }
165             } else if (!fromTokenizer.nextToken().equals(toTokenizer.nextToken())) {
166                 break;
167             }
168 
169             count++;
170         }
171 
172         // Reinitialize the tokenizers to count positions to retrieve the
173         // gobbled token
174 
175         toTokenizer = new StringTokenizer(toPath, String.valueOf(separatorChar));
176         fromTokenizer = new StringTokenizer(fromPath, String.valueOf(separatorChar));
177 
178         while (count-- > 0) {
179             fromTokenizer.nextToken();
180             toTokenizer.nextToken();
181         }
182 
183         StringBuilder relativePath = new StringBuilder();
184 
185         // add back refs for the rest of from location.
186         while (fromTokenizer.hasMoreTokens()) {
187             fromTokenizer.nextToken();
188 
189             relativePath.append("..");
190 
191             if (fromTokenizer.hasMoreTokens()) {
192                 relativePath.append(separatorChar);
193             }
194         }
195 
196         if (relativePath.length() != 0 && toTokenizer.hasMoreTokens()) {
197             relativePath.append(separatorChar);
198         }
199 
200         // add fwd fills for whatevers left of newPath.
201         while (toTokenizer.hasMoreTokens()) {
202             relativePath.append(toTokenizer.nextToken());
203 
204             if (toTokenizer.hasMoreTokens()) {
205                 relativePath.append(separatorChar);
206             }
207         }
208         return relativePath.toString();
209     }
210 }