]> source.dussan.org Git - archiva.git/blob
8c9a437a4bc55410c5573b1354029fcd907696d1
[archiva.git] /
1 package org.apache.archiva.common.utils;
2
3 /*
4  * Licensed to the Apache Software Foundation (ASF) under one
5  * or more contributor license agreements.  See the NOTICE file
6  * distributed with this work for additional information
7  * regarding copyright ownership.  The ASF licenses this file
8  * to you under the Apache License, Version 2.0 (the
9  * "License"); you may not use this file except in compliance
10  * with the License.  You may obtain a copy of the License at
11  *
12  *  http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing,
15  * software distributed under the License is distributed on an
16  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17  * KIND, either express or implied.  See the License for the
18  * specific language governing permissions and limitations
19  * under the License.
20  */
21
22 import org.apache.commons.lang3.StringUtils;
23
24 import java.net.MalformedURLException;
25 import java.net.URI;
26 import java.nio.file.FileSystems;
27 import java.nio.file.Path;
28 import java.nio.file.Paths;
29 import java.util.ArrayList;
30 import java.util.List;
31 import java.util.StringTokenizer;
32 import java.util.Vector;
33 import java.util.stream.StreamSupport;
34
35 /**
36  *
37  * PathUtil - simple utility methods for path manipulation.
38  *
39  * Some code is from Apache Ant SelectorUtils
40  *
41  */
42 public class PathUtil
43 {
44
45     public static final String DEEP_TREE_MATCH = "**";
46
47     public static String toUrl( String path )
48     {
49         // Is our work already done for us?
50         if ( path.startsWith( "file:/" ) )
51         {
52             return path;
53         }
54
55         return toUrl( Paths.get( path ) );
56     }
57
58     public static String toUrl( Path file )
59     {
60         try
61         {
62             return file.toUri().toURL().toExternalForm();
63         }
64         catch ( MalformedURLException e )
65         {
66             String pathCorrected = StringUtils.replaceChars( file.toAbsolutePath().toString(), '\\', '/' );
67             if ( pathCorrected.startsWith( "file:/" ) )
68             {
69                 return pathCorrected;
70             }
71
72             return "file://" + pathCorrected;
73         }
74     }
75
76     /**
77      * Given a basedir and a child file, return the relative path to the child.
78      *
79      * @param basedir the basedir.
80      * @param file    the file to get the relative path for.
81      * @return the relative path to the child. (NOTE: this path will NOT start with a file separator character)
82      */
83     public static String getRelative( Path basedir, Path file )
84     {
85         if (basedir.isAbsolute() && !file.isAbsolute()) {
86             return basedir.normalize().relativize(file.toAbsolutePath()).toString();
87         } else if (!basedir.isAbsolute() && file.isAbsolute()) {
88             return basedir.toAbsolutePath().relativize(file.normalize()).toString();
89         } else {
90             return basedir.normalize().relativize(file.normalize()).toString();
91         }
92     }
93
94     public static String getRelative(String basedir, Path file) {
95         return getRelative(Paths.get(basedir), file);
96     }
97
98     /**
99      * Given a basedir and a child file, return the relative path to the child.
100      *
101      * @param basedir the basedir.
102      * @param child   the child path (can be a full path)
103      * @return the relative path to the child. (NOTE: this path will NOT start with a file separator character)
104      */
105     public static String getRelative( String basedir, String child )
106     {
107
108         return getRelative(basedir, Paths.get(child));
109     }
110
111     /**
112      * Returns a path object from the given URI. If the URI has no scheme, the path of the URI is used
113      * for creating the filesystem path.
114      *
115      * @param uri the uri to convert
116      * @return a path object with the given path
117      * @throws java.nio.file.FileSystemNotFoundException if the uri scheme is not known.
118      */
119     public static Path getPathFromUri( URI uri) {
120         if (uri==null) {
121             return Paths.get("");
122         } else if (uri.getScheme()==null) {
123             return Paths.get(uri.getPath());
124         } else {
125             return Paths.get(uri);
126         }
127     }
128
129     public static boolean isAbsolutePath(String path) {
130         try
131         {
132             return Paths.get( path ).isAbsolute( );
133         } catch (Exception e) {
134             return false;
135         }
136     }
137
138     public static char getSeparatorChar() {
139         return FileSystems.getDefault( ).getSeparator( ).charAt( 0 );
140     }
141
142     public static String[] dissect(String pathString) {
143         Path path = Paths.get(pathString);
144         return StreamSupport.stream(path.spliterator(), false).map(Path::toString)
145             .toArray(String[]::new);
146     }
147
148     public static String separatorsToUnix(String path)
149     {
150         return path != null && path.indexOf( 92 ) != -1 ? path.replace( '\\', '/' ) : path;
151     }
152
153
154     /**
155      * Tests whether or not a given path matches a given pattern.
156      *
157      * If you need to call this method multiple times with the same
158      * pattern you should rather use TokenizedPath
159      *
160      * @param pattern The pattern to match against. Must not be
161      *                <code>null</code>.
162      * @param str     The path to match, as a String. Must not be
163      *                <code>null</code>.
164      *
165      * @return <code>true</code> if the pattern matches against the string,
166      *         or <code>false</code> otherwise.
167      */
168     public static boolean matchPath(String pattern, String str) {
169         String[] patDirs = tokenizePathAsArray(pattern);
170         return matchPath(patDirs, tokenizePathAsArray(str), true);
171     }
172
173     /**
174      * Tests whether or not a given path matches a given pattern.
175      *
176      * If you need to call this method multiple times with the same
177      * pattern you should rather use TokenizedPattern
178      *
179      *
180      * @param pattern The pattern to match against. Must not be
181      *                <code>null</code>.
182      * @param str     The path to match, as a String. Must not be
183      *                <code>null</code>.
184      * @param isCaseSensitive Whether or not matching should be performed
185      *                        case sensitively.
186      *
187      * @return <code>true</code> if the pattern matches against the string,
188      *         or <code>false</code> otherwise.
189      */
190     public static boolean matchPath(String pattern, String str,
191                                     boolean isCaseSensitive) {
192         String[] patDirs = tokenizePathAsArray(pattern);
193         return matchPath(patDirs, tokenizePathAsArray(str), isCaseSensitive);
194     }
195
196     /**
197      * Core implementation of matchPath.  It is isolated so that it
198      * can be called from TokenizedPattern.
199      */
200     static boolean matchPath(String[] tokenizedPattern, String[] strDirs,
201                              boolean isCaseSensitive) {
202         int patIdxStart = 0;
203         int patIdxEnd = tokenizedPattern.length - 1;
204         int strIdxStart = 0;
205         int strIdxEnd = strDirs.length - 1;
206
207         // up to first '**'
208         while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) {
209             String patDir = tokenizedPattern[patIdxStart];
210             if (patDir.equals(DEEP_TREE_MATCH)) {
211                 break;
212             }
213             if (!match(patDir, strDirs[strIdxStart], isCaseSensitive)) {
214                 return false;
215             }
216             patIdxStart++;
217             strIdxStart++;
218         }
219         if (strIdxStart > strIdxEnd) {
220             // String is exhausted
221             for (int i = patIdxStart; i <= patIdxEnd; i++) {
222                 if (!tokenizedPattern[i].equals(DEEP_TREE_MATCH)) {
223                     return false;
224                 }
225             }
226             return true;
227         }
228         if (patIdxStart > patIdxEnd) {
229             // String not exhausted, but pattern is. Failure.
230             return false;
231         }
232
233         // up to last '**'
234         while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) {
235             String patDir = tokenizedPattern[patIdxEnd];
236             if (patDir.equals(DEEP_TREE_MATCH)) {
237                 break;
238             }
239             if (!match(patDir, strDirs[strIdxEnd], isCaseSensitive)) {
240                 return false;
241             }
242             patIdxEnd--;
243             strIdxEnd--;
244         }
245         if (strIdxStart > strIdxEnd) {
246             // String is exhausted
247             for (int i = patIdxStart; i <= patIdxEnd; i++) {
248                 if (!tokenizedPattern[i].equals(DEEP_TREE_MATCH)) {
249                     return false;
250                 }
251             }
252             return true;
253         }
254
255         while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) {
256             int patIdxTmp = -1;
257             for (int i = patIdxStart + 1; i <= patIdxEnd; i++) {
258                 if (tokenizedPattern[i].equals(DEEP_TREE_MATCH)) {
259                     patIdxTmp = i;
260                     break;
261                 }
262             }
263             if (patIdxTmp == patIdxStart + 1) {
264                 // '**/**' situation, so skip one
265                 patIdxStart++;
266                 continue;
267             }
268             // Find the pattern between padIdxStart & padIdxTmp in str between
269             // strIdxStart & strIdxEnd
270             int patLength = (patIdxTmp - patIdxStart - 1);
271             int strLength = (strIdxEnd - strIdxStart + 1);
272             int foundIdx = -1;
273             strLoop:
274             for (int i = 0; i <= strLength - patLength; i++) {
275                 for (int j = 0; j < patLength; j++) {
276                     String subPat = tokenizedPattern[patIdxStart + j + 1];
277                     String subStr = strDirs[strIdxStart + i + j];
278                     if (!match(subPat, subStr, isCaseSensitive)) {
279                         continue strLoop;
280                     }
281                 }
282                 foundIdx = strIdxStart + i;
283                 break;
284             }
285             if (foundIdx == -1) {
286                 return false;
287             }
288
289             patIdxStart = patIdxTmp;
290             strIdxStart = foundIdx + patLength;
291         }
292
293         for (int i = patIdxStart; i <= patIdxEnd; i++) {
294             if (!DEEP_TREE_MATCH.equals(tokenizedPattern[i])) {
295                 return false;
296             }
297         }
298         return true;
299     }
300
301     /**
302      * Tests whether or not a string matches against a pattern.
303      * The pattern may contain two special characters:<br>
304      * '*' means zero or more characters<br>
305      * '?' means one and only one character
306      *
307      * @param pattern The pattern to match against.
308      *                Must not be <code>null</code>.
309      * @param str     The string which must be matched against the pattern.
310      *                Must not be <code>null</code>.
311      *
312      * @return <code>true</code> if the string matches against the pattern,
313      *         or <code>false</code> otherwise.
314      */
315     public static boolean match(String pattern, String str) {
316         return match(pattern, str, true);
317     }
318
319     /**
320      * Tests whether or not a string matches against a pattern.
321      * The pattern may contain two special characters:<br>
322      * '*' means zero or more characters<br>
323      * '?' means one and only one character
324      *
325      * @param pattern The pattern to match against.
326      *                Must not be <code>null</code>.
327      * @param str     The string which must be matched against the pattern.
328      *                Must not be <code>null</code>.
329      * @param caseSensitive Whether or not matching should be performed
330      *                        case sensitively.
331      *
332      *
333      * @return <code>true</code> if the string matches against the pattern,
334      *         or <code>false</code> otherwise.
335      */
336     public static boolean match(String pattern, String str,
337                                 boolean caseSensitive) {
338         char[] patArr = pattern.toCharArray();
339         char[] strArr = str.toCharArray();
340         int patIdxStart = 0;
341         int patIdxEnd = patArr.length - 1;
342         int strIdxStart = 0;
343         int strIdxEnd = strArr.length - 1;
344
345         boolean containsStar = false;
346         for (char ch : patArr) {
347             if (ch == '*') {
348                 containsStar = true;
349                 break;
350             }
351         }
352
353         if (!containsStar) {
354             // No '*'s, so we make a shortcut
355             if (patIdxEnd != strIdxEnd) {
356                 return false; // Pattern and string do not have the same size
357             }
358             for (int i = 0; i <= patIdxEnd; i++) {
359                 char ch = patArr[i];
360                 if (ch != '?' && different(caseSensitive, ch, strArr[i])) {
361                     return false; // Character mismatch
362                 }
363             }
364             return true; // String matches against pattern
365         }
366
367         if (patIdxEnd == 0) {
368             return true; // Pattern contains only '*', which matches anything
369         }
370
371         // Process characters before first star
372         while (true) {
373             char ch = patArr[patIdxStart];
374             if (ch == '*' || strIdxStart > strIdxEnd) {
375                 break;
376             }
377             if (ch != '?'
378                 && different(caseSensitive, ch, strArr[strIdxStart])) {
379                 return false; // Character mismatch
380             }
381             patIdxStart++;
382             strIdxStart++;
383         }
384         if (strIdxStart > strIdxEnd) {
385             // All characters in the string are used. Check if only '*'s are
386             // left in the pattern. If so, we succeeded. Otherwise failure.
387             return allStars(patArr, patIdxStart, patIdxEnd);
388         }
389
390         // Process characters after last star
391         while (true) {
392             char ch = patArr[patIdxEnd];
393             if (ch == '*' || strIdxStart > strIdxEnd) {
394                 break;
395             }
396             if (ch != '?' && different(caseSensitive, ch, strArr[strIdxEnd])) {
397                 return false; // Character mismatch
398             }
399             patIdxEnd--;
400             strIdxEnd--;
401         }
402         if (strIdxStart > strIdxEnd) {
403             // All characters in the string are used. Check if only '*'s are
404             // left in the pattern. If so, we succeeded. Otherwise failure.
405             return allStars(patArr, patIdxStart, patIdxEnd);
406         }
407
408         // process pattern between stars. padIdxStart and patIdxEnd point
409         // always to a '*'.
410         while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) {
411             int patIdxTmp = -1;
412             for (int i = patIdxStart + 1; i <= patIdxEnd; i++) {
413                 if (patArr[i] == '*') {
414                     patIdxTmp = i;
415                     break;
416                 }
417             }
418             if (patIdxTmp == patIdxStart + 1) {
419                 // Two stars next to each other, skip the first one.
420                 patIdxStart++;
421                 continue;
422             }
423             // Find the pattern between padIdxStart & padIdxTmp in str between
424             // strIdxStart & strIdxEnd
425             int patLength = (patIdxTmp - patIdxStart - 1);
426             int strLength = (strIdxEnd - strIdxStart + 1);
427             int foundIdx = -1;
428             strLoop:
429             for (int i = 0; i <= strLength - patLength; i++) {
430                 for (int j = 0; j < patLength; j++) {
431                     char ch = patArr[patIdxStart + j + 1];
432                     if (ch != '?' && different(caseSensitive, ch,
433                         strArr[strIdxStart + i + j])) {
434                         continue strLoop;
435                     }
436                 }
437                 foundIdx = strIdxStart + i;
438                 break;
439             }
440
441             if (foundIdx == -1) {
442                 return false;
443             }
444             patIdxStart = patIdxTmp;
445             strIdxStart = foundIdx + patLength;
446         }
447
448         // All characters in the string are used. Check if only '*'s are left
449         // in the pattern. If so, we succeeded. Otherwise failure.
450         return allStars(patArr, patIdxStart, patIdxEnd);
451     }
452
453     private static boolean allStars(char[] chars, int start, int end) {
454         for (int i = start; i <= end; ++i) {
455             if (chars[i] != '*') {
456                 return false;
457             }
458         }
459         return true;
460     }
461
462     private static boolean different(
463         boolean caseSensitive, char ch, char other) {
464         return caseSensitive
465             ? ch != other
466             : Character.toUpperCase(ch) != Character.toUpperCase(other);
467     }
468
469     /**
470      * Breaks a path up into a Vector of path elements, tokenizing on
471      * <code>File.separator</code>.
472      *
473      * @param path Path to tokenize. Must not be <code>null</code>.
474      *
475      * @return a Vector of path elements from the tokenized path
476      */
477     public static Vector<String> tokenizePath( String path) {
478         return tokenizePath(path, FileSystems.getDefault( ).getSeparator());
479     }
480
481     /**
482      * Breaks a path up into a Vector of path elements, tokenizing on
483      *
484      * @param path Path to tokenize. Must not be <code>null</code>.
485      * @param separator the separator against which to tokenize.
486      *
487      * @return a Vector of path elements from the tokenized path
488      * @since Ant 1.6
489      */
490     public static Vector<String> tokenizePath(String path, String separator) {
491         Vector<String> ret = new Vector<>();
492         if (isAbsolutePath(path)) {
493             String[] s = dissect(path);
494             ret.add(s[0]);
495             path = s[1];
496         }
497         StringTokenizer st = new StringTokenizer(path, separator);
498         while (st.hasMoreTokens()) {
499             ret.addElement(st.nextToken());
500         }
501         return ret;
502     }
503
504     /**
505      * Same as {@link #tokenizePath tokenizePath} but hopefully faster.
506      */
507     /* package */
508     static String[] tokenizePathAsArray(String path) {
509         String root = null;
510         if (isAbsolutePath(path)) {
511             String[] s = dissect(path);
512             root = s[0];
513             path = s[1];
514         }
515         char sep = getSeparatorChar();
516         int start = 0;
517         int len = path.length();
518         int count = 0;
519         for (int pos = 0; pos < len; pos++) {
520             if (path.charAt(pos) == sep) {
521                 if (pos != start) {
522                     count++;
523                 }
524                 start = pos + 1;
525             }
526         }
527         if (len != start) {
528             count++;
529         }
530         String[] l = new String[count + ((root == null) ? 0 : 1)];
531
532         if (root != null) {
533             l[0] = root;
534             count = 1;
535         } else {
536             count = 0;
537         }
538         start = 0;
539         for (int pos = 0; pos < len; pos++) {
540             if (path.charAt(pos) == sep) {
541                 if (pos != start) {
542                     String tok = path.substring(start, pos);
543                     l[count++] = tok;
544                 }
545                 start = pos + 1;
546             }
547         }
548         if (len != start) {
549             String tok = path.substring(start);
550             l[count/*++*/] = tok;
551         }
552         return l;
553     }
554
555
556 }