1/*
2 * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package jdk.internal.jrtfs;
27
28import java.util.regex.PatternSyntaxException;
29
30/**
31 * @implNote This class needs to maintain JDK 8 source compatibility.
32 *
33 * It is used internally in the JDK to implement jimage/jrtfs access,
34 * but also compiled and delivered as part of the jrtfs.jar to support access
35 * to the jimage file provided by the shipped JDK by tools running on JDK 8.
36 */
37final class JrtUtils {
38    private JrtUtils() {}
39
40    private static final String regexMetaChars = ".^$+{[]|()";
41    private static final String globMetaChars = "\\*?[{";
42    private static boolean isRegexMeta(char c) {
43        return regexMetaChars.indexOf(c) != -1;
44    }
45    private static boolean isGlobMeta(char c) {
46        return globMetaChars.indexOf(c) != -1;
47    }
48    private static final char EOL = 0;
49    private static char next(String glob, int i) {
50        if (i < glob.length()) {
51            return glob.charAt(i);
52        }
53        return EOL;
54    }
55
56    /*
57     * Creates a regex pattern from the given glob expression.
58     *
59     * @throws  PatternSyntaxException
60     */
61    public static String toRegexPattern(String globPattern) {
62        boolean inGroup = false;
63        StringBuilder regex = new StringBuilder("^");
64
65        int i = 0;
66        while (i < globPattern.length()) {
67            char c = globPattern.charAt(i++);
68            switch (c) {
69                case '\\':
70                    // escape special characters
71                    if (i == globPattern.length()) {
72                        throw new PatternSyntaxException("No character to escape",
73                                globPattern, i - 1);
74                    }
75                    char next = globPattern.charAt(i++);
76                    if (isGlobMeta(next) || isRegexMeta(next)) {
77                        regex.append('\\');
78                    }
79                    regex.append(next);
80                    break;
81                case '/':
82                    regex.append(c);
83                    break;
84                case '[':
85                    // don't match name separator in class
86                    regex.append("[[^/]&&[");
87                    if (next(globPattern, i) == '^') {
88                        // escape the regex negation char if it appears
89                        regex.append("\\^");
90                        i++;
91                    } else {
92                        // negation
93                        if (next(globPattern, i) == '!') {
94                            regex.append('^');
95                            i++;
96                        }
97                        // hyphen allowed at start
98                        if (next(globPattern, i) == '-') {
99                            regex.append('-');
100                            i++;
101                        }
102                    }
103                    boolean hasRangeStart = false;
104                    char last = 0;
105                    while (i < globPattern.length()) {
106                        c = globPattern.charAt(i++);
107                        if (c == ']') {
108                            break;
109                        }
110                        if (c == '/') {
111                            throw new PatternSyntaxException("Explicit 'name separator' in class",
112                                    globPattern, i - 1);
113                        }
114                        // TBD: how to specify ']' in a class?
115                        if (c == '\\' || c == '[' ||
116                                c == '&' && next(globPattern, i) == '&') {
117                            // escape '\', '[' or "&&" for regex class
118                            regex.append('\\');
119                        }
120                        regex.append(c);
121
122                        if (c == '-') {
123                            if (!hasRangeStart) {
124                                throw new PatternSyntaxException("Invalid range",
125                                        globPattern, i - 1);
126                            }
127                            if ((c = next(globPattern, i++)) == EOL || c == ']') {
128                                break;
129                            }
130                            if (c < last) {
131                                throw new PatternSyntaxException("Invalid range",
132                                        globPattern, i - 3);
133                            }
134                            regex.append(c);
135                            hasRangeStart = false;
136                        } else {
137                            hasRangeStart = true;
138                            last = c;
139                        }
140                    }
141                    if (c != ']') {
142                        throw new PatternSyntaxException("Missing ']", globPattern, i - 1);
143                    }
144                    regex.append("]]");
145                    break;
146                case '{':
147                    if (inGroup) {
148                        throw new PatternSyntaxException("Cannot nest groups",
149                                globPattern, i - 1);
150                    }
151                    regex.append("(?:(?:");
152                    inGroup = true;
153                    break;
154                case '}':
155                    if (inGroup) {
156                        regex.append("))");
157                        inGroup = false;
158                    } else {
159                        regex.append('}');
160                    }
161                    break;
162                case ',':
163                    if (inGroup) {
164                        regex.append(")|(?:");
165                    } else {
166                        regex.append(',');
167                    }
168                    break;
169                case '*':
170                    if (next(globPattern, i) == '*') {
171                        // crosses directory boundaries
172                        regex.append(".*");
173                        i++;
174                    } else {
175                        // within directory boundary
176                        regex.append("[^/]*");
177                    }
178                    break;
179                case '?':
180                   regex.append("[^/]");
181                   break;
182                default:
183                    if (isRegexMeta(c)) {
184                        regex.append('\\');
185                    }
186                    regex.append(c);
187            }
188        }
189        if (inGroup) {
190            throw new PatternSyntaxException("Missing '}", globPattern, i - 1);
191        }
192        return regex.append('$').toString();
193    }
194}
195