com.caucho.vfs.MergePath Maven / Gradle / Ivy
Show all versions of resin Show documentation
/*
* Copyright (c) 1998-2018 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.IOException;
import java.util.ArrayList;
import java.util.Map;
import com.caucho.loader.DynamicClassLoader;
import com.caucho.make.DependencyList;
import com.caucho.server.util.CauchoSystem;
/**
* A merging of several Paths used like a CLASSPATH. When the MergePath
* is opened for read, the first path in the list which contains the file will
* be the opened file. When the MergePath is opened for write, the first path
* in the list is used for the write.
*
* In the following example, "first" has priority over "second".
* If test.xml exists in both "first" and "second", the open will
* return "first/test.xml".
*
*
* MergePage merge = new MergePath();
* merge.addMergePath(Vfs.lookup("first");
* merge.addMergePath(Vfs.lookup("second");
*
* Path path = merge.lookup("test.xml");
* ReadStream is = path.openRead();
*
*
*
MergePath corresponds to the "merge:" Vfs schema
*
Path path = Vfs.lookup("merge:(../custom-foo;foo)");
*
*
* @since Resin 1.2
* @since Resin 3.0.10 merge: schema
*/
public class MergePath extends FilesystemPath {
private ArrayList _pathList;
/**
* Creates a new merge path.
*/
public MergePath()
{
this(null, "/", null, "/");
}
public MergePath(String userPath, String path, Path ... paths)
{
this(null, userPath, null, path);
if (paths != null) {
for (Path p : paths) {
addMergePath(p);
}
}
}
public MergePath(Path ... paths)
{
this(paths[0].getUserPath(), paths[0].getFullPath(), paths);
}
/**
* @param path canonical path
*/
protected MergePath(MergePath root,
String userPath, Map attributes,
String path)
{
super(root, userPath, path);
if (root == null) {
_root = this;
_pathList = new ArrayList();
}
}
/**
* schemeWalk is called by Path for a scheme lookup like file:/tmp/foo
*
* @param userPath the user's lookup() path
* @param attributes the user's attributes
* @param filePath the actual lookup() path
* @param offset offset into filePath
*/
@Override
public Path schemeWalk(String userPath,
Map attributes,
String filePath,
int offset)
{
int length = filePath.length();
if (length <= offset || filePath.charAt(offset) != '(')
return super.schemeWalk(userPath, attributes, filePath, offset);
MergePath mergePath = createMergePath();
mergePath.setUserPath(userPath);
int head = ++offset;
int tail = head;
while (tail < length) {
int ch = filePath.charAt(tail);
if (ch == ')') {
if (head + 1 != tail) {
String subPath = filePath.substring(head, tail);
if (subPath.startsWith("(") && subPath.endsWith(")"))
subPath = subPath.substring(1, subPath.length() - 1);
mergePath.addMergePath(Vfs.lookup(subPath));
}
if (tail + 1 == length)
return mergePath;
else
return mergePath.fsWalk(userPath, attributes, filePath.substring(tail + 1));
}
else if (ch == ';') {
String subPath = filePath.substring(head, tail);
if (subPath.startsWith("(") && subPath.endsWith(")"))
subPath = subPath.substring(1, subPath.length() - 1);
mergePath.addMergePath(Vfs.lookup(subPath));
head = ++tail;
}
else if (ch == '(') {
int depth = 1;
for (tail++; tail < length; tail++) {
if (filePath.charAt(tail) == '(')
depth++;
else if (filePath.charAt(tail) == ')') {
tail++;
depth--;
if (depth == 0)
break;
}
}
if (depth != 0)
return new NotFoundPath(getSchemeMap(), filePath);
}
else
tail++;
}
return new NotFoundPath(getSchemeMap(), filePath);
}
protected MergePath createMergePath()
{
return new MergePath();
}
/**
* Adds a new path to the end of the merge path.
*
* @param path the new path to search
*/
public void addMergePath(Path path)
{
if (! (path instanceof MergePath)) {
// Need to normalize so directory paths ends with a "./"
// XXX:
//if (path.isDirectory())
// path = path.lookup("./");
ArrayList pathList = ((MergePath) _root)._pathList;
if (! pathList.contains(path))
pathList.add(path);
}
else if (((MergePath) path)._root == _root)
return;
else {
MergePath mergePath = (MergePath) path;
ArrayList subPaths = mergePath.getMergePaths();
String pathName = "./" + mergePath._pathname + "/";
for (int i = 0; i < subPaths.size(); i++) {
Path subPath = subPaths.get(i);
addMergePath(subPath.lookup(pathName));
}
}
}
/**
* Adds the classpath as paths in the MergePath.
*/
public void addClassPath()
{
addClassPath(Thread.currentThread().getContextClassLoader());
}
/**
* Adds the classpath for the loader as paths in the MergePath.
*
* @param loader class loader whose classpath should be used to search.
*/
public void addClassPath(ClassLoader loader)
{
String classpath = null;
if (loader instanceof DynamicClassLoader)
classpath = ((DynamicClassLoader) loader).getClassPath();
else
classpath = CauchoSystem.getClassPath();
addMergePath(new ClasspathPath(loader));
addClassPath(classpath);
}
/**
* Adds the classpath for the loader as paths in the MergePath.
*
* @param loader class loader whose classpath should be used to search.
*/
public void addResourceClassPath(ClassLoader loader)
{
String classpath = null;
if (loader instanceof DynamicClassLoader)
classpath = ((DynamicClassLoader) loader).getResourcePathSpecificFirst();
else
classpath = CauchoSystem.getClassPath();
addMergePath(new ClasspathPath(loader));
addClassPath(classpath);
}
/**
* Adds the classpath as paths in the MergePath.
*/
public void addLocalClassPath()
{
addLocalClassPath(Thread.currentThread().getContextClassLoader());
}
/**
* Adds the classpath for the loader as paths in the MergePath.
*
* @param loader class loader whose classpath should be used to search.
*/
public void addLocalClassPath(ClassLoader loader)
{
String classpath = null;
if (loader instanceof DynamicClassLoader)
classpath = ((DynamicClassLoader) loader).getLocalClassPath();
else
classpath = System.getProperty("java.class.path");
addClassPath(classpath);
}
/**
* Adds the classpath for the loader as paths in the MergePath.
*
* @param classpath class loader whose classpath should be used to search.
*/
public void addClassPath(String classpath)
{
char sep = CauchoSystem.getPathSeparatorChar();
int head = 0;
int tail = 0;
while (head < classpath.length()) {
tail = classpath.indexOf(sep, head);
String segment = null;
if (tail < 0) {
segment = classpath.substring(head);
head = classpath.length();
}
else {
segment = classpath.substring(head, tail);
head = tail + 1;
}
if (segment.equals(""))
continue;
else if (segment.endsWith(".jar") || segment.endsWith(".zip"))
addMergePath(JarPath.create(Vfs.lookup(segment)));
else
addMergePath(Vfs.lookup(segment));
}
}
/**
* Return the list of paths searched in the merge path.
*/
public ArrayList getMergePaths()
{
return ((MergePath) _root)._pathList;
}
/**
* Walking down the path just extends the path. It won't be evaluated
* until opening.
*/
public Path fsWalk(String userPath,
Map attributes,
String path)
{
ArrayList pathList = getMergePaths();
if (! userPath.startsWith("/") || pathList.size() == 0)
return new MergePath((MergePath) _root, userPath, attributes, path);
String bestPrefix = null;
for (int i = 0; i < pathList.size(); i++) {
Path subPath = pathList.get(i);
String prefix = subPath.getPath();
if (path.startsWith(prefix) &&
(bestPrefix == null || bestPrefix.length() < prefix.length())) {
bestPrefix = prefix;
}
}
if (bestPrefix != null) {
path = path.substring(bestPrefix.length());
if (! path.startsWith("/"))
path = "/" + path;
return new MergePath((MergePath) _root, userPath, attributes, path);
}
return pathList.get(0).lookup(userPath, attributes);
}
/**
* Returns the scheme of the best path.
*/
public String getScheme()
{
return getBestPath().getScheme();
}
/**
* Returns the full path name of the best path.
*/
/*
@Override
public String getFullPath()
{
Path path = getBestPath();
return path.getFullPath();
}
*/
/**
* Returns the full native path name of the best path.
*/
/*
@Override
public String getNativePath()
{
Path path = getBestPath();
return path.getNativePath();
}
*/
/**
* Returns the URL of the best path.
*/
/*
@Override
public String getURL()
{
Path path = getBestPath();
if (! path.exists()) {
path = getWritePath();
}
return path.getURL();
}
*/
/**
* Returns the relative path into the merge path.
*/
public String getRelativePath()
{
if (_pathname.startsWith("/"))
return "." + _pathname;
else
return _pathname;
}
/**
* True if any file matching this path exists.
*/
public boolean exists()
{
return getBestPath().exists();
}
/**
* Returns equivalent of struct stat.st_mode if appropriate.
*/
@Override
public int getMode()
{
return getBestPath().getMode();
}
/**
* Tests if the path is marked as executable
*/
@Override
public boolean isExecutable()
{
return getBestPath().isExecutable();
}
/**
* True if the best path is a directory.
*/
public boolean isDirectory()
{
return getBestPath().isDirectory();
}
/**
* True if the best path is a file.
*/
public boolean isFile()
{
return getBestPath().isFile();
}
/**
* Returns the length of the best path.
*/
public long getLength()
{
return getBestPath().getLength();
}
/**
* Returns the last modified time of the best path.
*/
public long getLastModified()
{
return getBestPath().getLastModified();
}
/**
* Returns true if the best path can be read.
*/
public boolean canRead()
{
return getBestPath().canRead();
}
/**
* Returns true if the best path can be written to.
*/
public boolean canWrite()
{
// overriding by shadowing
// return getBestPath().canWrite();
return getBestPath().canRead();
}
/**
* Returns all the resources matching the path.
*/
public ArrayList getResources(String pathName)
{
ArrayList list = new ArrayList();
String pathname = _pathname;
// XXX: why was this here?
if (pathname.startsWith("/"))
pathname = "." + pathname;
ArrayList pathList = ((MergePath) _root)._pathList;
for (int i = 0; i < pathList.size(); i++) {
Path path = pathList.get(i);
path = path.lookup(pathname);
ArrayList subResources = path.getResources(pathName);
for (int j = 0; j < subResources.size(); j++) {
Path newPath = subResources.get(j);
if (! list.contains(newPath))
list.add(newPath);
}
}
return list;
}
/**
* Returns all the resources matching the path.
*/
public ArrayList getResources()
{
ArrayList list = new ArrayList();
String pathname = _pathname;
// XXX: why?
if (pathname.startsWith("/"))
pathname = "." + pathname;
ArrayList pathList = ((MergePath) _root)._pathList;
for (int i = 0; i < pathList.size(); i++) {
Path path = pathList.get(i);
path = path.lookup(pathname);
ArrayList subResources = path.getResources();
for (int j = 0; j < subResources.size(); j++) {
Path newPath = subResources.get(j);
if (! list.contains(newPath))
list.add(newPath);
}
}
return list;
}
/**
* List the merged directories.
*/
public String []list() throws IOException
{
ArrayList list = new ArrayList();
String pathname = _pathname;
// XXX:??
if (pathname.startsWith("/"))
pathname = "." + pathname;
ArrayList pathList = ((MergePath) _root)._pathList;
for (int i = 0; i < pathList.size(); i++) {
Path path = pathList.get(i);
path = path.lookup(pathname);
if (path.isDirectory()) {
String[]subList = path.list();
for (int j = 0; subList != null && j < subList.length; j++) {
if (! list.contains(subList[j]))
list.add(subList[j]);
}
}
}
return (String []) list.toArray(new String[list.size()]);
}
/**
* XXX: Probably should mkdir in the first path
*/
@Override
public boolean mkdir()
throws IOException
{
return getWritePath().mkdir();
}
/**
* XXX: Probably should mkdir in the first path
*/
@Override
public boolean mkdirs()
throws IOException
{
return getWritePath().mkdirs();
}
/**
* Remove the matching path.
*/
public boolean remove()
throws IOException
{
return getBestPath().remove();
}
/**
* Renames the path.
*/
public boolean renameTo(Path path)
throws IOException
{
return getBestPath().renameTo(path);
}
/**
* Opens the best path for reading.
*/
@Override
public StreamImpl openReadImpl() throws IOException
{
StreamImpl stream = getBestPath().openReadImpl();
stream.setPath(this);
return stream;
}
/**
* Opens the best path for writing. XXX: If the best path doesn't
* exist, this should probably create the file in the first path.
*/
@Override
public StreamImpl openWriteImpl() throws IOException
{
StreamImpl stream = getWritePath().openWriteImpl();
stream.setPath(this);
return stream;
}
/**
* Opens the best path for reading and writing. XXX: If the best path
* doesn't exist, this should probably create the file in the first path.
*/
@Override
public StreamImpl openReadWriteImpl() throws IOException
{
StreamImpl stream = getWritePath().openReadWriteImpl();
stream.setPath(this);
return stream;
}
/**
* Opens the best path for appending. XXX: If the best path
* doesn't exist, this should probably create the file in the first path.
*/
@Override
public StreamImpl openAppendImpl() throws IOException
{
StreamImpl stream = getWritePath().openAppendImpl();
stream.setPath(this);
return stream;
}
/**
* Opens the best path for random access.
*/
@Override
public RandomAccessStream openFileRandomAccess() throws IOException
{
RandomAccessStream stream = getWritePath().openFileRandomAccess();
// stream.setPath(this);
return stream;
}
/**
* Returns the first matching path.
*/
public Path getWritePath()
{
ArrayList pathList = getPathList();
if (pathList.size() == 0)
return new NotFoundPath(getSchemeMap(), _pathname);
else {
return pathList.get(0);
}
}
/**
* Creates a dependency.
*/
@Override
public PersistentDependency createDepend()
{
ArrayList pathList = ((MergePath) _root)._pathList;
if (pathList.size() == 1)
return pathList.get(0).createDepend();
DependencyList dependList = new DependencyList();
for (int i = 0; i < pathList.size(); i++) {
Path path = pathList.get(i);
Path realPath = path.lookup(_pathname);
dependList.add(realPath.createDepend());
}
return dependList;
}
/**
* Returns the first matching path.
*/
public Path getBestPath()
{
/*
if (_bestPath != null)
return _bestPath;
*/
ArrayList pathList = getPathList();
for (int i = 0; i < pathList.size(); i++) {
Path path = pathList.get(i);
if (path.exists()) {
return path;
}
}
if (pathList.size() > 0) {
return pathList.get(0);
}
return new NotFoundPath(getSchemeMap(), _userPath);
}
protected ArrayList getPathList()
{
if (_pathList == null) {
String pathname = _pathname;
// XXX:??
if (pathname.startsWith("/"))
pathname = "." + pathname;
ArrayList pathList = new ArrayList();
ArrayList rootPathList = ((MergePath) _root)._pathList;
for (int i = 0; i < rootPathList.size(); i++) {
Path rootPath = rootPathList.get(i);
Path realPath = rootPath.lookup(pathname);
realPath.setUserPath(_userPath);
pathList.add(realPath);
}
_pathList = pathList;
}
return _pathList;
}
@Override
public Path copy()
{
MergePath root = (MergePath) _root;
if (root == this) {
root = null;
}
else {
root = (MergePath) root.copy();
}
MergePath copy = new MergePath(root, _userPath, null, _pathname);
ArrayList pathList = getPathList();
for (Path path : pathList) {
copy.addMergePath(path.copy());
}
return copy;
}
/**
* Returns a name for the path
*/
public String toString()
{
return getClass().getSimpleName() + "[" + _pathname + "]";
}
}