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.nio.file.Path;
43  import java.util.StringTokenizer;
44  
45  /**
46   * This class defines utilities methods helping to determine path-related information such as relative paths.
47   * <p>
48   * The original code comes from <b>org.codehaus.plexus.util.PathTool</b>.
49   *
50   * @author Ibrahim Chaehoi
51   * @author <a href="mailto:pete-apache-dev@kazmier.com">Pete Kazmier</a>
52   * @author <a href="mailto:vmassol@apache.org">Vincent Massol</a>
53   * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a>
54   */
55  public class PathUtils {
56  
57      /**
58       * Instantiates a new path utils.
59       */
60      private PathUtils() {
61          // Prevent Instantiation
62      }
63  
64      /**
65       * This method can calculate the relative path between two pathes on a file system.
66       *
67       * <pre>
68       * PathUtils.getRelativeFilePath( null, null )                                   = ""
69       * PathUtils.getRelativeFilePath( null, "/usr/local/java/bin" )                  = ""
70       * PathUtils.getRelativeFilePath( "/usr/local", null )                           = ""
71       * PathUtils.getRelativeFilePath( "/usr/local", "/usr/local/java/bin" )          = "java/bin"
72       * PathUtils.getRelativeFilePath( "/usr/local", "/usr/local/java/bin/" )         = "java/bin"
73       * PathUtils.getRelativeFilePath( "/usr/local/java/bin", "/usr/local/" )         = "../.."
74       * PathUtils.getRelativeFilePath( "/usr/local/", "/usr/local/java/bin/java.sh" ) = "java/bin/java.sh"
75       * PathUtils.getRelativeFilePath( "/usr/local/java/bin/java.sh", "/usr/local/" ) = "../../.."
76       * PathUtils.getRelativeFilePath( "/usr/local/", "/bin" )                        = "../../bin"
77       * PathUtils.getRelativeFilePath( "/bin", "/usr/local/" )                        = "../usr/local"
78       * </pre>
79       *
80       * Note: On Windows based system, the <code>/</code> character should be replaced by <code>\</code> character.
81       *
82       * @param oldPath
83       *            the old path
84       * @param newPath
85       *            the new path
86       *
87       * @return a relative file path from <code>oldPath</code>.
88       */
89      public static final String getRelativeFilePath(final String oldPath, final String newPath) {
90          if (Strings.isNullOrEmpty(oldPath) || Strings.isNullOrEmpty(newPath)) {
91              return "";
92          }
93  
94          // Normalize the path delimiters
95          String fromPath = Path.of(oldPath).toString();
96          String toPath = Path.of(newPath).toString();
97  
98          // strip any leading slashes if its a windows path
99          if (toPath.matches("^\\[a-zA-Z]:")) {
100             toPath = toPath.substring(1);
101         }
102         if (fromPath.matches("^\\[a-zA-Z]:")) {
103             fromPath = fromPath.substring(1);
104         }
105 
106         // lowercase windows drive letters.
107         if (fromPath.startsWith(":", 1)) {
108             fromPath = Character.toLowerCase(fromPath.charAt(0)) + fromPath.substring(1);
109         }
110         if (toPath.startsWith(":", 1)) {
111             toPath = Character.toLowerCase(toPath.charAt(0)) + toPath.substring(1);
112         }
113 
114         // check for the presence of windows drives. No relative way of
115         // traversing from one to the other.
116         if (toPath.startsWith(":", 1) && fromPath.startsWith(":", 1)
117                 && !toPath.substring(0, 1).equals(fromPath.substring(0, 1))) {
118             // they both have drive path element but they dont match, no relative path
119             return null;
120         }
121 
122         if ((toPath.startsWith(":", 1) && !fromPath.startsWith(":", 1))
123                 || (!toPath.startsWith(":", 1) && fromPath.startsWith(":", 1))) {
124             // one has a drive path element and the other doesn't, no relative path.
125             return null;
126         }
127 
128         String resultPath = buildRelativePath(toPath, fromPath, File.separatorChar);
129 
130         if (newPath.endsWith(File.separator) && !resultPath.endsWith(File.separator)) {
131             return resultPath + File.separator;
132         }
133 
134         return resultPath;
135     }
136 
137     // ----------------------------------------------------------------------
138     // Private methods
139     // ----------------------------------------------------------------------
140 
141     /**
142      * Builds the relative path.
143      *
144      * @param toPath
145      *            the to path
146      * @param fromPath
147      *            the from path
148      * @param separatorChar
149      *            the separator char
150      *
151      * @return the string
152      */
153     private static String buildRelativePath(String toPath, String fromPath, final char separatorChar) {
154         // use tokenizer to traverse paths and for lazy checking
155         StringTokenizer toTokenizer = new StringTokenizer(toPath, String.valueOf(separatorChar));
156         StringTokenizer fromTokenizer = new StringTokenizer(fromPath, String.valueOf(separatorChar));
157 
158         int count = 0;
159 
160         // walk along the to path looking for divergence from the from path
161         while (toTokenizer.hasMoreTokens() && fromTokenizer.hasMoreTokens()) {
162             if (separatorChar == '\\') {
163                 if (!fromTokenizer.nextToken().equalsIgnoreCase(toTokenizer.nextToken())) {
164                     break;
165                 }
166             } else if (!fromTokenizer.nextToken().equals(toTokenizer.nextToken())) {
167                 break;
168             }
169 
170             count++;
171         }
172 
173         // Reinitialize the tokenizers to count positions to retrieve the
174         // gobbled token
175 
176         toTokenizer = new StringTokenizer(toPath, String.valueOf(separatorChar));
177         fromTokenizer = new StringTokenizer(fromPath, String.valueOf(separatorChar));
178 
179         while (count-- > 0) {
180             fromTokenizer.nextToken();
181             toTokenizer.nextToken();
182         }
183 
184         StringBuilder relativePath = new StringBuilder();
185 
186         // add back refs for the rest of from location.
187         while (fromTokenizer.hasMoreTokens()) {
188             fromTokenizer.nextToken();
189 
190             relativePath.append("..");
191 
192             if (fromTokenizer.hasMoreTokens()) {
193                 relativePath.append(separatorChar);
194             }
195         }
196 
197         if (relativePath.length() != 0 && toTokenizer.hasMoreTokens()) {
198             relativePath.append(separatorChar);
199         }
200 
201         // add fwd fills for whatevers left of newPath.
202         while (toTokenizer.hasMoreTokens()) {
203             relativePath.append(toTokenizer.nextToken());
204 
205             if (toTokenizer.hasMoreTokens()) {
206                 relativePath.append(separatorChar);
207             }
208         }
209         return relativePath.toString();
210     }
211 }