/* * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved * * This file is part of Resin(R) Open Source * * Each copy or derived work must preserve the copyright notice and this * notice unmodified. * * Resin Open Source is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Resin Open Source is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty * of NON-INFRINGEMENT. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with Resin Open Source; if not, write to the * * Free Software Foundation, Inc. * 59 Temple Place, Suite 330 * Boston, MA 02111-1307 USA * * @author Scott Ferguson */ package com.caucho.vfs; import com.caucho.util.CharBuffer; import com.caucho.util.Crc64; import com.caucho.util.L10N; import com.caucho.util.LruCache; import com.caucho.util.RandomUtil; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.net.URL; import java.net.URLDecoder; import java.security.cert.Certificate; import java.util.ArrayList; import java.util.Iterator; import java.util.Locale; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; /** * A virtual filesystem path, essentially represented by a URL. * Its API resembles a combination of the JDK File object and the URL object. * * <p>Paths are, in general, given with the canonical file separator of * forward slash, '/'. The filesystems will take care of any necessary * translation. * * <p>Currently available filesystems: * <dl> * <dt>file:/path/to/file<dd>Java file * <dt>http://host:port/path/name?query<dd>HTTP request * <dt>tcp://host:port<dd>Raw TCP connection * <dt>mailto:user@host?subject=foo&cc=user2<dd>Mail to a user. * <dt>log:/group/subgroup/item<dd>Logging based on the configuration file. * <dt>stdout:<dd>System.out * <dt>stderr:<dd>System.err * <dt>null:<dd>The equivalent of /dev/null * </dl> */ public abstract class Path { protected final static L10N L = new L10N(Path.class); private static final Integer LOCK = new Integer(0); private static final LruCache<PathKey,Path> _pathLookupCache = new LruCache<PathKey,Path>(8192); private static boolean _isTestWindows; protected static char _separatorChar = File.separatorChar; protected static char _pathSeparatorChar = File.pathSeparatorChar; private static String _newline; private static final AtomicReference<PathKey> _key = new AtomicReference<PathKey>(); private static final SchemeMap DEFAULT_SCHEME_MAP = new SchemeMap(); private static SchemeMap _defaultSchemeMap; static long _startTime; protected SchemeMap _schemeMap = _defaultSchemeMap; /** * Creates a new Path object. * * @param root the new Path root. */ protected Path(Path root) { if (root != null) _schemeMap = root._schemeMap; else if (_defaultSchemeMap != null) _schemeMap = _defaultSchemeMap; else _schemeMap = DEFAULT_SCHEME_MAP; } /** * Creates a new Path object. * * @param root the new Path root. */ protected Path(SchemeMap map) { _schemeMap = map; } /** * Looks up a new path based on the old path. * * @param name relative url to the new path * @return The new path. */ public final Path lookup(String name) { return lookup(name, null); } /** * Looks up a path by a URL. */ public final Path lookup(URL url) { String name = URLDecoder.decode(url.toString()); return lookup(name, null); } /** * Returns a new path relative to the current one. * * <p>Path only handles scheme:xxx. Subclasses of Path will specialize * the xxx. * * @param userPath relative or absolute path, essentially any url. * @param newAttributes attributes for the new path. * * @return the new path or null if the scheme doesn't exist */ public Path lookup(String userPath, Map<String,Object> newAttributes) { if (newAttributes != null) return lookupImpl(userPath, newAttributes); else if (userPath == null) return this; Path path = getCache(userPath); if (path != null) return path; path = lookupImpl(userPath, null); if (_startTime == 0) { _startTime = System.currentTimeMillis(); putCache(userPath, path); } return path; } protected Path getCache(String subPath) { if (! isPathCacheable()) return null; PathKey key = _key.getAndSet(null); if (key == null) key = new PathKey(); key.init(this, subPath); Path path = _pathLookupCache.get(key); _key.set(key); if (path != null) return path.cacheCopy(); else return null; } protected void putCache(String subPath, Path path) { if (! isPathCacheable()) return; Path copy = path.cacheCopy(); if (copy != null) { _pathLookupCache.putIfNew(new PathKey(this, subPath), copy); } } /** * Returns true if the path itself is cacheable */ protected boolean isPathCacheable() { return false; } /** * Returns a new path relative to the current one. * * <p>Path only handles scheme:xxx. Subclasses of Path will specialize * the xxx. * * @param userPath relative or absolute path, essentially any url. * @param newAttributes attributes for the new path. * * @return the new path or null if the scheme doesn't exist */ public Path lookupImpl(String userPath, Map<String,Object> newAttributes) { if (userPath == null) return lookupImpl(getPath(), newAttributes); String scheme = scanScheme(userPath); if (scheme == null) return schemeWalk(userPath, newAttributes, userPath, 0); Path path; SchemeMap schemeMap = _schemeMap; // Special case to handle the windows special schemes // c:xxx -> file:/c:xxx if (isWindows()) { int length = scheme.length(); int ch; if (length == 1 && ('a' <= (ch = scheme.charAt(0)) && ch <= 'z' || 'A' <= ch && ch <= 'Z')) { if (_isTestWindows) return schemeWalk(userPath, newAttributes, "/" + userPath, 0); path = schemeMap.get("file"); if (path != null) return path.schemeWalk(userPath, newAttributes, "/" + userPath, 0); else return schemeWalk(userPath, newAttributes, "/" + userPath, 0); } } path = schemeMap.get(scheme); // assume the foo:bar is a subfile if (path == null) return schemeWalk(userPath, newAttributes, userPath, 0); return path.schemeWalk(userPath, newAttributes, userPath, scheme.length() + 1); } /** * Looks up a path using the local filesystem conventions. e.g. on * Windows, a name of 'd:\foo\bar\baz.html' will look up the baz.html * on drive d. * * @param name relative url using local filesystem separators. */ public final Path lookupNative(String name) { return lookupNative(name, null); } /** * Looks up a native path, adding attributes. */ public Path lookupNative(String name, Map<String,Object> attributes) { return lookup(name, attributes); } /** * Returns a native path relative to this native path if the passed path * is relative to this path, or an absolute path if the passed path is not * relative to this path. */ public String lookupRelativeNativePath(Path path) { String thisNative = getNativePath(); String pathNative = path.getNativePath(); if (pathNative.startsWith(thisNative)) { int i = thisNative.length(); while (i < pathNative.length()) { if (pathNative.charAt(i) != getFileSeparatorChar()) break; i++; } return i == pathNative.length() ? "" : pathNative.substring(i); } else return pathNative; } /** * Looks up all the resources matching a name. (Generally only useful * with MergePath. */ public ArrayList<Path> getResources(String name) { ArrayList<Path> list = new ArrayList<Path>(); Path path = lookup(name); if (path.exists()) list.add(path); return list; } /** * Looks up all the existing resources. (Generally only useful * with MergePath. */ public ArrayList<Path> getResources() { ArrayList<Path> list = new ArrayList<Path>(); //if (exists()) list.add(this); return list; } /** * Returns the parent path. */ public Path getParent() { return this; } /** * Returns the scheme portion of a uri. Since schemes are case-insensitive, * normalize them to lower case. */ protected String scanScheme(String uri) { int i = 0; if (uri == null) return null; int length = uri.length(); if (length == 0) return null; int ch = uri.charAt(0); if (ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z') { for (i = 1; i < length; i++) { ch = uri.charAt(i); if (ch == ':') return uri.substring(0, i).toLowerCase(Locale.ENGLISH); if (! (ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || ch >= '0' && ch <= '0' || ch == '+' || ch == '-' || ch == '.')) break; } } return null; } /** * Path-specific lookup. Path implementations will override this. * * @param userPath the user's lookup() path. * @param newAttributes the attributes for the new path. * @param newPath the lookup() path * @param offset offset into newPath to start lookup. * * @return the found path */ abstract public Path schemeWalk(String userPath, Map<String,Object> newAttributes, String newPath, int offset); /** * Returns the full url for the given path. */ public String getURL() { return escapeURL(getScheme() + ":" + getFullPath()); } /** * Returns the url scheme */ public abstract String getScheme(); /** * Returns the schemeMap */ public SchemeMap getSchemeMap() { return _schemeMap; } /** * Returns the hostname */ public String getHost() { throw new UnsupportedOperationException(); } /** * Returns the port. */ public int getPort() { throw new UnsupportedOperationException(); } /** * Returns the path. e.g. for HTTP, returns the part after the * host and port. */ public abstract String getPath(); /** * Returns the last segment of the path. * * <p>e.g. for http://www.caucho.com/products/index.html, getTail() * returns 'index.html' */ public String getTail() { return ""; } /** * Returns the query string of the path. */ public String getQuery() { throw new UnsupportedOperationException(); } /** * Returns the native representation of the path. * * On Windows, getNativePath() returns 'd:\\foo\bar.html', * getPath() returns '/d:/foo/bar.html' */ public String getNativePath() { return getFullPath(); } /** * Returns the last string used as a lookup, if available. This allows * parsers to give intelligent error messages, with the user's path * instead of the whole path. * * The following will print '../test.html': * <code><pre> * Path path = Pwd.lookup("/some/dir").lookup("../test.html"); * System.out.println(path.getUserPath()); * </pre></code> * */ public String getUserPath() { return getPath(); } /** * Sets the user path. Useful for temporary files caching another * URL. */ public void setUserPath(String userPath) { } /** * Returns the full path, including the restricted root. * * <p>For the following, path.getPath() returns '/file.html', while * path.getFullPath() returns '/chroot/file.html'. * <code><pre> * Path chroot = Pwd.lookup("/chroot").createRoot(); * Path path = chroot.lookup("/file.html"); * </pre></code> */ public String getFullPath() { return getPath(); } /** * For union paths like MergePath, return the relative path into * that path. */ public String getRelativePath() { return getPath(); } /** * Returns true for windows security issues. */ public boolean isWindowsInsecure() { String lower = getPath().toLowerCase(Locale.ENGLISH); int lastCh; if ((lastCh = lower.charAt(lower.length() - 1)) == '.' || lastCh == ' ' || lastCh == '*' || lastCh == '?' || ((lastCh == '/' || lastCh == '\\') && ! isDirectory()) || lower.endsWith("::$data") || isWindowsSpecial(lower, "/con") || isWindowsSpecial(lower, "/aux") || isWindowsSpecial(lower, "/prn") || isWindowsSpecial(lower, "/nul") || isWindowsSpecial(lower, "/com1") || isWindowsSpecial(lower, "/com2") || isWindowsSpecial(lower, "/com3") || isWindowsSpecial(lower, "/com4") || isWindowsSpecial(lower, "/lpt1") || isWindowsSpecial(lower, "/lpt2") || isWindowsSpecial(lower, "/lpt3")) { return true; } return false; } private boolean isWindowsSpecial(String lower, String test) { int p = lower.indexOf(test); if (p < 0) return false; int lowerLen = lower.length(); int testLen = test.length(); char ch; if (lowerLen == p + testLen || (ch = lower.charAt(p + testLen)) == '/' || ch == '.') return true; else return false; } /** * Returns any signing certificates, e.g. for jar signing. */ public Certificate []getCertificates() { return null; } /** * Tests if the file exists. */ public boolean exists() { return false; } /** * Returns the mime-type of the file. * <p>Mime-type ignorant filesystems return 'application/octet-stream' */ public String getContentType() { return "application/octet-stream"; } /** * Tests if the path refers to a directory. */ public boolean isDirectory() { return false; } /** * Tests if the path refers to a file. */ public boolean isFile() { return false; } /** * Tests if the path refers to a symbolic link. */ public boolean isLink() { return false; } /** * Tests if the path refers to a socket. */ public boolean isSocket() { return false; } /** * Tests if the path refers to a FIFO. */ public boolean isFIFO() { return false; } /** * Tests if the path refers to a block device. */ public boolean isBlockDevice() { return false; } /** * Tests if the path refers to a block device. */ public boolean isCharacterDevice() { return false; } /** * Tests if the path is marked as executable */ public boolean isExecutable() { return false; } /** * Change the executable status of the of the path. * * @throws UnsupportedOperationException */ public boolean setExecutable(boolean isExecutable) { return false; } /** * Tests if the path refers to a symbolic link. */ public boolean isSymbolicLink() { return false; } /** * Tests if the path refers to a hard link. */ public boolean isHardLink() { return false; } /** * Tests if the path refers to an object. */ public boolean isObject() { return false; } /** * Clears any status cache */ public void clearStatusCache() { } /** * Returns the length of the file in bytes. * @return 0 for non-files */ public long getLength() { return 0; } /** * Returns the last modified time of the file. According to the jdk, * this may not correspond to the system time. * @return 0 for non-files. */ public long getLastModified() { return 0; } public void setLastModified(long time) { } /** * Returns the last access time of the file. * * @return 0 for non-files. */ public long getLastAccessTime() { return getLastModified(); } /** * Returns the create time of the file. * * @return 0 for non-files. */ public long getCreateTime() { return getLastModified(); } /** * Tests if the file can be read. */ public boolean canRead() { return false; } /** * Tests if the file can be written. */ public boolean canWrite() { return false; } // // POSIX stat() related calls // /** * Returns equivalent of struct stat.st_dev if appropriate. */ public long getDevice() { return 0; } /** * Returns equivalent of struct stat.st_ino if appropriate. */ public long getInode() { return 0; } /** * Returns equivalent of struct stat.st_mode if appropriate. */ public int getMode() { return 0; } /** * Returns equivalent of struct stat.st_nlink if appropriate. */ public int getNumberOfLinks() { return 0; } /** * Returns equivalent of struct stat.st_uid if appropriate. */ public int getUser() { return 0; } /** * Returns equivalent of struct stat.st_gid if appropriate. */ public int getGroup() { return 0; } /** * Returns equivalent of struct stat.st_rdev if appropriate. */ public long getDeviceId() { return 0; } /** * Returns equivalent of struct stat.st_blksize if appropriate. */ public long getBlockSize() { return 0; } /** * Returns equivalent of struct stat.st_blocks if appropriate. */ public long getBlockCount() { return 0; } /** * Returns equivalent of struct stat.st_ctime if appropriate. */ public long getLastStatusChangeTime() { return 0; } /** * Tests if the file can be read. */ public boolean canExecute() { return canRead(); } /** * Changes the group */ public boolean changeGroup(int gid) throws IOException { return false; } /** * Changes the group */ public boolean changeGroup(String groupName) throws IOException { return false; } /** * Changes the permissions * * @return true if successful */ public boolean chmod(int value) { return false; } public int getOwner() { return getUser(); } /** * Changes the owner * * @return true if successful */ public boolean changeOwner(int uid) throws IOException { return false; } /** * Changes the owner * * @return true if successful */ public boolean changeOwner(String ownerName) throws IOException { return false; } public long getDiskSpaceFree() { return 0; } public long getDiskSpaceTotal() { return 0; } /** * @return The contents of this directory or null if the path does not * refer to a directory. */ public String []list() throws IOException { return new String[0]; } /** * Returns a jdk1.2 Iterator for the contents of this directory. */ public Iterator<String> iterator() throws IOException { String list[] = list(); // Avoids NPE when subclasses override list() and // possibly return null, e.g. JarPath. if (list == null) list = new String[0]; return new ArrayIterator(list); } /** * Creates the directory named by this path. * @return true if successful. */ public boolean mkdir() throws IOException { return false; } /** * Creates the directory named by this path and any parent directories. * @return true if successful. */ public boolean mkdirs() throws IOException { return false; } /** * Removes the file or directory named by this path. * * @return true if successful */ public boolean remove() throws IOException { return false; } /** * Removes the all files and directories below this path. * * @return true if successful. */ public boolean removeAll() throws IOException { if (isDirectory()) { String []list = list(); for (int i = 0; i < list.length; i++) { Path subpath = lookup(list[i]); subpath.removeAll(); } } return remove(); } /** * Sets the length of the file to zero. * * @return true if successful */ public boolean truncate() throws IOException { return truncate(0); } /** * Sets the length of the file. * * @return true if successful */ public boolean truncate(long length) throws IOException { if (length == 0) { if (exists()) { StreamImpl stream = openWriteImpl(); stream.close(); clearStatusCache(); return true; } else return false; } else throw new UnsupportedOperationException(getClass().getName() + ": truncate"); } /** * Renames the file or directory to the name given by the path. * @return true if successful */ public boolean renameTo(Path path) throws IOException { return false; } /** * Renames the file or directory to the name given by the path. * @return true if successful */ public final boolean renameTo(String path) throws IOException { return renameTo(lookup(path)); } /** * Creates a restricted root, like the Unix chroot call. * Restricted roots cannot access schemes, so file:/etc/passwd cannot * be used. * * <p>createRoot is useful for restricting JavaScript scripts without * resorting to the dreadfully slow security manager. */ public Path createRoot() { return createRoot(SchemeMap.getNullSchemeMap()); } public Path createRoot(SchemeMap schemeMap) { throw new UnsupportedOperationException("createRoot"); } /** * Binds the context to the current path. Later lookups will return * the new context instead of the current path. Essentially, this is a * software symbolic link. */ public void bind(Path context) { throw new UnsupportedOperationException("bind"); } /** * unbinds a link. */ public void unbind() { throw new UnsupportedOperationException("unbind"); } /** * Gets the object at the path. Normal filesystems will generally * typically return null. * * <p>A bean filesystem or a mime-type aware filesystem could deserialize * the contents of the file. */ public Object getValue() throws Exception { throw new UnsupportedOperationException("getValue"); } /** * Sets the object at the path. * * <p>Normal filesystems will generally do nothing. However, a bean * filesystem or a mime-type aware filesystem could serialize the object * and store it. */ public void setValue(Object obj) throws Exception { throw new UnsupportedOperationException("setValue"); } /** * Gets an attribute of the object. */ public Object getAttribute(String name) throws IOException { return null; } /** * Returns a iterator of all attribute names set for this object. * @return null if path has no attributes. */ public Iterator getAttributeNames() throws IOException { return null; } /** * Opens a resin ReadStream for reading. */ public final ReadStream openRead() throws IOException { clearStatusCache(); StreamImpl impl = openReadImpl(); impl.setPath(this); return new ReadStream(impl); } /** * Opens a resin WriteStream for writing. */ public final WriteStream openWrite() throws IOException { clearStatusCache(); StreamImpl impl = openWriteImpl(); impl.setPath(this); return new WriteStream(impl); } /** * Opens a resin ReadWritePair for reading and writing. * * <p>A chat channel, for example, would open its socket using this * interface. */ public ReadWritePair openReadWrite() throws IOException { return openReadWrite(false); } /** * Opens a resin ReadWritePair for reading and writing. * * <p>A chat channel, for example, would open its socket using this * interface. */ public ReadWritePair openReadWrite(boolean isAutoFlush) throws IOException { clearStatusCache(); StreamImpl impl = openReadWriteImpl(); impl.setPath(this); WriteStream writeStream = new WriteStream(impl); ReadStream readStream; if (isAutoFlush) readStream = new ReadStream(impl, writeStream); else readStream = new ReadStream(impl, null); return new ReadWritePair(readStream, writeStream); } /** * Opens a resin ReadWritePair for reading and writing. * * <p>A chat channel, for example, would open its socket using this * interface. * * @param is pre-allocated ReadStream to be initialized * @param os pre-allocated WriteStream to be initialized */ public void openReadWrite(ReadStream is, WriteStream os) throws IOException { clearStatusCache(); StreamImpl impl = openReadWriteImpl(); impl.setPath(this); os.init(impl); is.init(impl, os); } /** * Opens a resin stream for appending. */ public WriteStream openAppend() throws IOException { clearStatusCache(); StreamImpl impl = openAppendImpl(); return new WriteStream(impl); } /** * Opens a random-access stream. */ public RandomAccessStream openMemoryMappedFile(long fileSize) throws IOException { return null; } /** * Opens a random-access stream. */ public RandomAccessStream openRandomAccess() throws IOException { return openFileRandomAccess(); } /** * Opens a random-access stream. */ public RandomAccessStream openFileRandomAccess() throws IOException { clearStatusCache(); throw new UnsupportedOperationException(getClass().getName()); } /** * Creates the file named by this Path and returns true if the * file is new. */ public boolean createNewFile() throws IOException { synchronized (LOCK) { if (! exists()) { clearStatusCache(); WriteStream s = openWrite(); s.close(); return true; } } return false; } /** * Creates a dependency. */ public PersistentDependency createDepend() { return new Depend(this); } /** * Creates a unique temporary file as a child of this directory. * * @param prefix filename prefix * @param suffix filename suffix, defaults to .tmp * @return Path to the new file. */ public Path createTempFile(String prefix, String suffix) throws IOException { if (prefix == null || prefix.length () == 0) prefix = "t"; if (suffix == null) suffix = ".tmp"; synchronized (LOCK) { for (int i = 0; i < 32768; i++) { int r = Math.abs((int) RandomUtil.getRandomLong()); Path file = lookup(prefix + r + suffix); if (file.createNewFile()) return file; } } throw new IOException("cannot create temp file"); } /** * Creates a link named by this path to another path. * * @param target the target of the link * @param hardLink true if the link should be a hard link */ public boolean createLink(Path target, boolean hardLink) throws IOException { throw new UnsupportedOperationException(getScheme() + ": doesn't support createLink"); } /** * Returns the target path from the link. * Returns null for a non-link. */ public String readLink() { return null; } /** * Returns the actual path from the link. */ public String realPath() { return getFullPath(); } /** * Utility to write the contents of this path to the destination stream. * * @param os destination stream. */ public void writeToStream(OutputStream os) throws IOException { StreamImpl is = openReadImpl(); TempBuffer tempBuffer = TempBuffer.allocate(); try { byte []buffer = tempBuffer.getBuffer(); int length = buffer.length; int len; while ((len = is.read(buffer, 0, length)) > 0) { os.write(buffer, 0, len); } } finally { TempBuffer.free(tempBuffer); tempBuffer = null; is.close(); } } /** * Utility to write the contents of this path to the destination stream. * * @param os destination stream. */ public void writeToStream(OutputStreamWithBuffer os) throws IOException { StreamImpl is = openReadImpl(); try { byte []buffer = os.getBuffer(); int offset = os.getBufferOffset(); int length = buffer.length; while (! os.isClosed()) { int sublen = length - offset; if (sublen <= 0) { buffer = os.nextBuffer(offset); offset = os.getBufferOffset(); sublen = length - offset; } sublen = is.read(buffer, offset, sublen); if (sublen <= 0) { os.setBufferOffset(offset); return; } offset += sublen; } } finally { is.close(); } } /** * Returns the crc64 code. */ public long getCrc64() { try { if (isDirectory()) { String []list = list(); long digest = 0; for (int i = 0; i < list.length; i++) { digest = Crc64.generate(digest, list[i]); } return digest; } else if (canRead()) { ReadStream is = openRead(); try { long digest = 0; byte []buffer = is.getBuffer(); while (is.fillBuffer() > 0) { int length = is.getLength(); digest = Crc64.generate(digest, buffer, 0, length); } return digest; } finally { is.close(); } } else { return -1; // Depend requires -1 } } catch (IOException e) { // XXX: log e.printStackTrace(); return -1; } } /** * Returns the object at this path. Normally, only paths like JNDI * will support this. */ public Object getObject() throws IOException { throw new UnsupportedOperationException(getScheme() + ": doesn't support getObject"); } /** * Sets the object at this path. Normally, only paths like JNDI * will support this. */ public void setObject(Object obj) throws IOException { throw new UnsupportedOperationException(getScheme() + ": doesn't support setObject"); } @Override public int hashCode() { return toString().hashCode(); } @Override public boolean equals(Object o) { if (this == o) return true; else if (! (o instanceof Path)) return false; else return getURL().equals(((Path) o).getURL()); } public String toString() { return getFullPath(); } public StreamImpl openReadImpl() throws IOException { throw new UnsupportedOperationException("openRead:" + getClass().getName()); } public StreamImpl openWriteImpl() throws IOException { throw new UnsupportedOperationException("openWrite:" + getClass().getName()); } public StreamImpl openReadWriteImpl() throws IOException { throw new UnsupportedOperationException("openReadWrite:" + getClass().getName()); } public StreamImpl openAppendImpl() throws IOException { throw new UnsupportedOperationException("openAppend:" + getClass().getName()); } protected static String escapeURL(String rawURL) { CharBuffer cb = null; int length = rawURL.length(); for (int i = 0; i < length; i++) { char ch = rawURL.charAt(i); switch (ch) { case ' ': if (cb == null) { cb = new CharBuffer(); cb.append(rawURL, 0, i); } cb.append("%20"); break; case '#': if (cb == null) { cb = new CharBuffer(); cb.append(rawURL, 0, i); } cb.append("%23"); break; case '%': if (cb == null) { cb = new CharBuffer(); cb.append(rawURL, 0, i); } cb.append("%25"); break; case '+': if (cb == null) { cb = new CharBuffer(); cb.append(rawURL, 0, i); } cb.append("%2B"); break; default: if (cb != null) cb.append(ch); break; } } if (cb != null) return cb.toString(); else return rawURL; } protected Path copy() { return this; } /** * Copy for caching. */ protected Path cacheCopy() { return this; } public static final void setDefaultSchemeMap(SchemeMap schemeMap) { _defaultSchemeMap = schemeMap; _pathLookupCache.clear(); } public static final boolean isWindows() { return _separatorChar == '\\' || _isTestWindows; } public static final void setTestWindows(boolean isTest) { _isTestWindows = isTest; } protected static final char getSeparatorChar() { return _separatorChar; } public static final char getFileSeparatorChar() { return _separatorChar; } public static final char getPathSeparatorChar() { return _pathSeparatorChar; } protected static String getUserDir() { return System.getProperty("user.dir"); } public static String getNewlineString() { if (_newline == null) { _newline = System.getProperty("line.separator"); if (_newline == null) _newline = "\n"; } return _newline; } private class ArrayIterator implements Iterator<String> { String []list; int index; public boolean hasNext() { return index < list.length; } public String next() { return index < list.length ? list[index++] : null; } public void remove() { throw new UnsupportedOperationException(); } ArrayIterator(String []list) { this.list = list; index = 0; } } static class PathKey { private Path _parent; private String _lookup; PathKey() { } PathKey(Path parent, String lookup) { _parent = parent; _lookup = lookup; } void init(Path parent, String lookup) { _parent = parent; _lookup = lookup; } public int hashCode() { if (_parent != null) return _parent.hashCode() * 65521 + _lookup.hashCode(); else return _lookup.hashCode(); } public boolean equals(Object test) { if (! (test instanceof PathKey)) return false; PathKey key = (PathKey) test; if (_parent != null) return (_parent.equals(key._parent) && _lookup.equals(key._lookup)); else return (key._parent == null && _lookup.equals(key._lookup)); } } static { DEFAULT_SCHEME_MAP.put("file", new FilePath(null)); //DEFAULT_SCHEME_MAP.put("jar", new JarScheme(null)); DEFAULT_SCHEME_MAP.put("http", new HttpPath("127.0.0.1", 0)); DEFAULT_SCHEME_MAP.put("https", new HttpsPath("127.0.0.1", 0)); DEFAULT_SCHEME_MAP.put("tcp", new TcpPath(null, null, null, "127.0.0.1", 0)); DEFAULT_SCHEME_MAP.put("tcps", new TcpsPath(null, null, null, "127.0.0.1", 0)); StreamImpl stdout = StdoutStream.create(); StreamImpl stderr = StderrStream.create(); DEFAULT_SCHEME_MAP.put("stdout", stdout.getPath()); DEFAULT_SCHEME_MAP.put("stderr", stderr.getPath()); VfsStream nullStream = new VfsStream(null, null); DEFAULT_SCHEME_MAP.put("null", new ConstPath(null, nullStream)); DEFAULT_SCHEME_MAP.put("jndi", new JndiPath()); } }