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 }