• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

hazendaz / smartsprites / 266

03 May 2025 07:20PM UTC coverage: 84.619%. Remained the same
266

push

github

hazendaz
Use path over file and fix test that would now break with modern usage (had extra /)

533 of 658 branches covered (81.0%)

Branch coverage included in aggregate %.

1277 of 1481 relevant lines covered (86.23%)

0.86 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

66.35
/src/main/java/org/carrot2/util/PathUtils.java
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)) {
1!
91
            return "";
×
92
        }
93

94
        // Normalize the path delimiters
95
        String fromPath = Path.of(oldPath).toString();
1✔
96
        String toPath = Path.of(newPath).toString();
1✔
97

98
        // strip any leading slashes if its a windows path
99
        if (toPath.matches("^\\[a-zA-Z]:")) {
1!
100
            toPath = toPath.substring(1);
×
101
        }
102
        if (fromPath.matches("^\\[a-zA-Z]:")) {
1!
103
            fromPath = fromPath.substring(1);
×
104
        }
105

106
        // lowercase windows drive letters.
107
        if (fromPath.startsWith(":", 1)) {
1!
108
            fromPath = Character.toLowerCase(fromPath.charAt(0)) + fromPath.substring(1);
×
109
        }
110
        if (toPath.startsWith(":", 1)) {
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)
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))
1!
123
                || (!toPath.startsWith(":", 1) && fromPath.startsWith(":", 1))) {
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);
1✔
129

130
        if (newPath.endsWith(File.separator) && !resultPath.endsWith(File.separator)) {
1!
131
            return resultPath + File.separator;
×
132
        }
133

134
        return resultPath;
1✔
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));
1✔
156
        StringTokenizer fromTokenizer = new StringTokenizer(fromPath, String.valueOf(separatorChar));
1✔
157

158
        int count = 0;
1✔
159

160
        // walk along the to path looking for divergence from the from path
161
        while (toTokenizer.hasMoreTokens() && fromTokenizer.hasMoreTokens()) {
1✔
162
            if (separatorChar == '\\') {
1!
163
                if (!fromTokenizer.nextToken().equalsIgnoreCase(toTokenizer.nextToken())) {
×
164
                    break;
×
165
                }
166
            } else if (!fromTokenizer.nextToken().equals(toTokenizer.nextToken())) {
1✔
167
                break;
1✔
168
            }
169

170
            count++;
1✔
171
        }
172

173
        // Reinitialize the tokenizers to count positions to retrieve the
174
        // gobbled token
175

176
        toTokenizer = new StringTokenizer(toPath, String.valueOf(separatorChar));
1✔
177
        fromTokenizer = new StringTokenizer(fromPath, String.valueOf(separatorChar));
1✔
178

179
        while (count-- > 0) {
1✔
180
            fromTokenizer.nextToken();
1✔
181
            toTokenizer.nextToken();
1✔
182
        }
183

184
        StringBuilder relativePath = new StringBuilder();
1✔
185

186
        // add back refs for the rest of from location.
187
        while (fromTokenizer.hasMoreTokens()) {
1✔
188
            fromTokenizer.nextToken();
1✔
189

190
            relativePath.append("..");
1✔
191

192
            if (fromTokenizer.hasMoreTokens()) {
1!
193
                relativePath.append(separatorChar);
×
194
            }
195
        }
196

197
        if (relativePath.length() != 0 && toTokenizer.hasMoreTokens()) {
1✔
198
            relativePath.append(separatorChar);
1✔
199
        }
200

201
        // add fwd fills for whatevers left of newPath.
202
        while (toTokenizer.hasMoreTokens()) {
1✔
203
            relativePath.append(toTokenizer.nextToken());
1✔
204

205
            if (toTokenizer.hasMoreTokens()) {
1✔
206
                relativePath.append(separatorChar);
1✔
207
            }
208
        }
209
        return relativePath.toString();
1✔
210
    }
211
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc