1/*
2 * Copyright (c) 2008, 2011, 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 sun.nio.fs;
27
28import java.nio.file.InvalidPathException;
29
30/**
31 * A parser of Windows path strings
32 */
33
34class WindowsPathParser {
35    private WindowsPathParser() { }
36
37    /**
38     * The result of a parse operation
39     */
40    static class Result {
41        private final WindowsPathType type;
42        private final String root;
43        private final String path;
44
45        Result(WindowsPathType type, String root, String path) {
46            this.type = type;
47            this.root = root;
48            this.path = path;
49        }
50
51        /**
52         * The path type
53         */
54        WindowsPathType type() {
55            return type;
56        }
57
58        /**
59         * The root component
60         */
61        String root() {
62            return root;
63        }
64
65        /**
66         * The normalized path (includes root)
67         */
68        String path() {
69            return path;
70        }
71    }
72
73    /**
74     * Parses the given input as a Windows path
75     */
76    static Result parse(String input) {
77        return parse(input, true);
78    }
79
80    /**
81     * Parses the given input as a Windows path where it is known that the
82     * path is already normalized.
83     */
84    static Result parseNormalizedPath(String input) {
85        return parse(input, false);
86    }
87
88    /**
89     * Parses the given input as a Windows path.
90     *
91     * @param   requireToNormalize
92     *          Indicates if the path requires to be normalized
93     */
94    private static Result parse(String input, boolean requireToNormalize) {
95        String root = "";
96        WindowsPathType type = null;
97
98        int len = input.length();
99        int off = 0;
100        if (len > 1) {
101            char c0 = input.charAt(0);
102            char c1 = input.charAt(1);
103            char c = 0;
104            int next = 2;
105            if (isSlash(c0) && isSlash(c1)) {
106                // UNC: We keep the first two slash, collapse all the
107                // following, then take the hostname and share name out,
108                // meanwhile collapsing all the redundant slashes.
109                type = WindowsPathType.UNC;
110                off = nextNonSlash(input, next, len);
111                next = nextSlash(input, off, len);
112                if (off == next)
113                    throw new InvalidPathException(input, "UNC path is missing hostname");
114                String host = input.substring(off, next);  //host
115                off = nextNonSlash(input, next, len);
116                next = nextSlash(input, off, len);
117                if (off == next)
118                    throw new InvalidPathException(input, "UNC path is missing sharename");
119                root = "\\\\" + host + "\\" + input.substring(off, next) + "\\";
120                off = next;
121            } else {
122                if (isLetter(c0) && c1 == ':') {
123                    char c2;
124                    if (len > 2 && isSlash(c2 = input.charAt(2))) {
125                        // avoid concatenation when root is "D:\"
126                        if (c2 == '\\') {
127                            root = input.substring(0, 3);
128                        } else {
129                            root = input.substring(0, 2) + '\\';
130                        }
131                        off = 3;
132                        type = WindowsPathType.ABSOLUTE;
133                    } else {
134                        root = input.substring(0, 2);
135                        off = 2;
136                        type = WindowsPathType.DRIVE_RELATIVE;
137                    }
138                }
139            }
140        }
141        if (off == 0) {
142            if (len > 0 && isSlash(input.charAt(0))) {
143                type = WindowsPathType.DIRECTORY_RELATIVE;
144                root = "\\";
145            } else {
146                type = WindowsPathType.RELATIVE;
147            }
148        }
149
150        if (requireToNormalize) {
151            StringBuilder sb = new StringBuilder(input.length());
152            sb.append(root);
153            return new Result(type, root, normalize(sb, input, off));
154        } else {
155            return new Result(type, root, input);
156        }
157    }
158
159    /**
160     * Remove redundant slashes from the rest of the path, forcing all slashes
161     * into the preferred slash.
162    */
163    private static String normalize(StringBuilder sb, String path, int off) {
164        int len = path.length();
165        off = nextNonSlash(path, off, len);
166        int start = off;
167        char lastC = 0;
168        while (off < len) {
169            char c = path.charAt(off);
170            if (isSlash(c)) {
171                if (lastC == ' ')
172                    throw new InvalidPathException(path,
173                                                   "Trailing char <" + lastC + ">",
174                                                   off - 1);
175                sb.append(path, start, off);
176                off = nextNonSlash(path, off, len);
177                if (off != len)   //no slash at the end of normalized path
178                    sb.append('\\');
179                start = off;
180            } else {
181                if (isInvalidPathChar(c))
182                    throw new InvalidPathException(path,
183                                                   "Illegal char <" + c + ">",
184                                                   off);
185                lastC = c;
186                off++;
187            }
188        }
189        if (start != off) {
190            if (lastC == ' ')
191                throw new InvalidPathException(path,
192                                               "Trailing char <" + lastC + ">",
193                                               off - 1);
194            sb.append(path, start, off);
195        }
196        return sb.toString();
197    }
198
199    private static final boolean isSlash(char c) {
200        return (c == '\\') || (c == '/');
201    }
202
203    private static final int nextNonSlash(String path, int off, int end) {
204        while (off < end && isSlash(path.charAt(off))) { off++; }
205        return off;
206    }
207
208    private static final int nextSlash(String path, int off, int end) {
209        char c;
210        while (off < end && !isSlash(c=path.charAt(off))) {
211            if (isInvalidPathChar(c))
212                throw new InvalidPathException(path,
213                                               "Illegal character [" + c + "] in path",
214                                               off);
215            off++;
216        }
217        return off;
218    }
219
220    private static final boolean isLetter(char c) {
221        return ((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z'));
222    }
223
224    // Reserved characters for window path name
225    private static final String reservedChars = "<>:\"|?*";
226    private static final boolean isInvalidPathChar(char ch) {
227        return ch < '\u0020' || reservedChars.indexOf(ch) != -1;
228    }
229}
230