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 }