com.caucho.vfs.Path Maven / Gradle / Ivy
Show all versions of resin-kernel Show documentation
/*
* Copyright (c) 1998-2012 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 java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.channels.FileChannel;
import java.nio.file.OpenOption;
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;
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;
/**
* A virtual filesystem path, essentially represented by a URL.
* Its API resembles a combination of the JDK File object and the URL object.
*
* Paths are, in general, given with the canonical file separator of
* forward slash, '/'. The filesystems will take care of any necessary
* translation.
*
*
Currently available filesystems:
*
* - file:/path/to/file
- Java file
*
- http://host:port/path/name?query
- HTTP request
*
- tcp://host:port
- Raw TCP connection
*
- mailto:user@host?subject=foo&cc=user2
- Mail to a user.
*
- log:/group/subgroup/item
- Logging based on the configuration file.
*
- stdout:
- System.out
*
- stderr:
- System.err
*
- null:
- The equivalent of /dev/null
*
*/
public abstract class Path implements Comparable {
protected final static L10N L = new L10N(Path.class);
private static final Integer LOCK = new Integer(0);
private static final LruCache _pathLookupCache
= new LruCache(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 _key
= new AtomicReference();
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
_schemeMap = getDefaultSchemeMap();
}
private SchemeMap getDefaultSchemeMap()
{
synchronized (Path.class) {
if (_defaultSchemeMap == null) {
_defaultSchemeMap = new SchemeMap();
createDefaultSchemeMap(_defaultSchemeMap);
}
return _defaultSchemeMap;
}
}
/**
* 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 new path based on the old path.
*
* @param name relative url to the new path
* @return The new path.
*/
public final Path lookupChild(String name)
{
return lookupImpl(name, null, false);
}
/**
* 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.
*
* 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 newAttributes)
{
if (newAttributes != null)
return lookupImpl(userPath, newAttributes, true);
else if (userPath == null)
return this;
Path path = getCache(userPath);
if (path != null) {
return path;
}
path = lookupImpl(userPath, null, true);
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.
*
* 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 newAttributes,
boolean isAllowRoot)
{
if (userPath == null) {
return lookupImpl(getPath(), newAttributes, isAllowRoot);
}
if (! isAllowRoot) {
return schemeWalk(userPath, newAttributes, userPath, 0);
}
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();
char ch;
if (length == 1
&& ('a' <= (ch = scheme.charAt(0)) && ch <= 'z'
|| 'A' <= ch && ch <= 'Z')) {
userPath = "" + Character.toLowerCase(ch) + userPath.substring(1);
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 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 getResources(String name)
{
ArrayList list = new ArrayList();
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 getResources()
{
ArrayList list = new ArrayList();
//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 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.
*
* 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':
*
* Path path = Pwd.lookup("/some/dir").lookup("../test.html");
* System.out.println(path.getUserPath());
*
*
*/
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.
*
*
For the following, path.getPath() returns '/file.html', while
* path.getFullPath() returns '/chroot/file.html'.
*
* Path chroot = Pwd.lookup("/chroot").createRoot();
* Path path = chroot.lookup("/file.html");
*
*/
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.
*
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 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() && ! isLink()) {
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.
*
* 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.
*
*
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.
*
*
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.
*
*
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.
*
*
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.
*
*
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;
}
public FileChannelFactory fileChannelFactory()
{
throw new UnsupportedOperationException(getClass().getName());
}
/**
* 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();
}
}
/**
* Utility to write the contents of this path to the destination stream.
*
* @param os destination stream.
*/
public void sendfile(OutputStream os, long offset, long length)
throws IOException
{
if (os instanceof OutputStreamWithBuffer) {
writeToStream((OutputStreamWithBuffer) os);
}
else {
writeToStream(os);
}
}
/**
* 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;
}
public Path copy()
{
return this;
}
public Path unwrap()
{
return this;
}
/**
* Copy for caching.
*/
protected Path cacheCopy()
{
return this;
}
@Override
public int compareTo(Path path)
{
return getURL().compareTo(path.getURL());
}
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 []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;
}
}
public interface FileChannelFactory
{
FileChannel openFileChannel(OpenOption ...options) throws IOException;
}
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));
}
}
private static void createDefaultSchemeMap(SchemeMap map)
{
map.put("file", new FilePath(null));
//DEFAULT_SCHEME_MAP.put("jar", new JarScheme(null));
map.put("http", new HttpPath("127.0.0.1", 0));
map.put("https", new HttpsPath("127.0.0.1", 0));
map.put("tcp", new TcpPath(null, null, null, "127.0.0.1", 0));
map.put("tcps", new TcpsPath(null, null, null, "127.0.0.1", 0));
StreamImpl stdout = StdoutStream.create();
StreamImpl stderr = StderrStream.create();
map.put("stdout", stdout.getPath());
map.put("stderr", stderr.getPath());
VfsStream nullStream = new VfsStream(null, null);
map.put("null", new ConstPath(null, nullStream));
map.put("jndi", new JndiPath());
}
}