001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.vfs2.provider.local;
018
019import org.apache.commons.vfs2.FileName;
020import org.apache.commons.vfs2.FileSystemException;
021import org.apache.commons.vfs2.FileType;
022
023/**
024 * A parser for Windows file names.
025 */
026public class WindowsFileNameParser extends LocalFileNameParser {
027
028    /**
029     * Constructs a new instance.
030     */
031    public WindowsFileNameParser() {
032        // empty
033    }
034
035    @Override
036    protected FileName createFileName(final String scheme, final String rootFile, final String path,
037            final FileType type) {
038        return new WindowsFileName(scheme, rootFile, path, type);
039    }
040
041    /**
042     * Extracts a drive prefix from a path. Leading '/' chars have been removed.
043     */
044    private String extractDrivePrefix(final StringBuilder name) {
045        // Looking for <letter> ':' '/'
046        if (name.length() < 3) {
047            // Too short
048            return null;
049        }
050        final char ch = name.charAt(0);
051        if (ch == '/' || ch == ':') {
052            // Missing drive letter
053            return null;
054        }
055        if (name.charAt(1) != ':') {
056            // Missing ':'
057            return null;
058        }
059        if (name.charAt(2) != '/') {
060            // Missing separator
061            return null;
062        }
063
064        final String prefix = name.substring(0, 2);
065        name.delete(0, 2);
066
067        return prefix.intern();
068    }
069
070    /**
071     * Pops the root prefix off a URI, which has had the scheme removed.
072     */
073    @Override
074    protected String extractRootPrefix(final String uri, final StringBuilder name) throws FileSystemException {
075        return extractWindowsRootPrefix(uri, name);
076    }
077
078    /**
079     * Extracts a UNC name from a path. Leading '/' chars have been removed.
080     */
081    private String extractUNCPrefix(final String uri, final StringBuilder name) throws FileSystemException {
082        // Looking for <name> '/' <name> ( '/' | <end> )
083
084        // Look for first separator
085        final int maxpos = name.length();
086        int pos = 0;
087        while (pos < maxpos && name.charAt(pos) != '/') {
088            pos++;
089        }
090        pos++;
091        if (pos >= maxpos) {
092            throw new FileSystemException("vfs.provider.local/missing-share-name.error", uri);
093        }
094
095        // Now have <name> '/'
096        final int startShareName = pos;
097        while (pos < maxpos && name.charAt(pos) != '/') {
098            pos++;
099        }
100        if (pos == startShareName) {
101            throw new FileSystemException("vfs.provider.local/missing-share-name.error", uri);
102        }
103
104        // Now have <name> '/' <name> ( '/' | <end> )
105        final String prefix = name.substring(0, pos);
106        name.delete(0, pos);
107        return prefix;
108    }
109
110    /**
111     * Extracts a Windows root prefix from a name.
112     */
113    private String extractWindowsRootPrefix(final String uri, final StringBuilder name) throws FileSystemException {
114        // Looking for:
115        // ('/'){0, 3} <letter> ':' '/'
116        // ['/'] '//' <name> '/' <name> ( '/' | <end> )
117
118        // Skip over first 4 (unc) leading '/' chars
119        int startPos = 0;
120        final int maxlen = Math.min(4, name.length());
121        while (startPos < maxlen && name.charAt(startPos) == '/') {
122            startPos++;
123        }
124        if (startPos == maxlen && name.length() > startPos + 1 && name.charAt(startPos + 1) == '/') {
125            // Too many '/'
126            throw new FileSystemException("vfs.provider.local/not-absolute-file-name.error", uri);
127        }
128        name.delete(0, startPos);
129
130        // Look for drive name
131        final String driveName = extractDrivePrefix(name);
132        if (driveName != null) {
133            return driveName;
134        }
135
136        // Look for UNC name
137        if (startPos < 2) {
138            throw new FileSystemException("vfs.provider.local/not-absolute-file-name.error", uri);
139        }
140
141        return "//" + extractUNCPrefix(uri, name);
142    }
143}