Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* JBoss, Home of Professional Open Source
* Copyright 2006, JBoss Inc., and individual contributors as indicated
* by the @authors tag.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jboss.vfs;
import static org.jboss.vfs.VFSMessages.MESSAGES;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLStreamHandler;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.jboss.vfs.protocol.FileURLStreamHandler;
import org.jboss.vfs.protocol.VirtualFileURLStreamHandler;
import org.jboss.vfs.spi.MountHandle;
import org.jboss.vfs.util.PaddedManifestStream;
import org.jboss.vfs.util.PathTokenizer;
import org.jboss.vfs.util.automount.Automounter;
/**
* VFS Utilities
*
* @author Adrian Brock
* @author Ales Justin
* @author David M. Lloyd
* @version $Revision: 1.1 $
*/
public class VFSUtils {
/**
* The default encoding
*/
private static final String DEFAULT_ENCODING = "UTF-8";
/**
* Constant representing the URL vfs protocol
*/
public static final String VFS_PROTOCOL = "vfs";
/**
* The {@link URLStreamHandler} for the 'vfs' protocol
*/
public static final URLStreamHandler VFS_URL_HANDLER = new VirtualFileURLStreamHandler();
/**
* The {@link URLStreamHandler} for the 'file' protocol
*/
public static final URLStreamHandler FILE_URL_HANDLER = new FileURLStreamHandler();
/**
* The default buffer size to use for copies
*/
public static final int DEFAULT_BUFFER_SIZE = 65536;
private VFSUtils() {
}
/**
* Get the paths string for a collection of virtual files
*
* @param paths the paths
* @return the string
* @throws IllegalArgumentException for null paths
*/
public static String getPathsString(Collection paths) {
if (paths == null) {
throw MESSAGES.nullArgument("paths");
}
StringBuilder buffer = new StringBuilder();
boolean first = true;
for (VirtualFile path : paths) {
if (path == null) { throw new IllegalArgumentException("Null path in " + paths); }
if (first == false) {
buffer.append(':');
} else {
first = false;
}
buffer.append(path.getPathName());
}
if (first == true) {
buffer.append("");
}
return buffer.toString();
}
/**
* Add manifest paths
*
* @param file the file
* @param paths the paths to add to
* @throws IOException if there is an error reading the manifest or the virtual file is closed
* @throws IllegalStateException if the file has no parent
* @throws IllegalArgumentException for a null file or paths
*/
public static void addManifestLocations(VirtualFile file, List paths) throws IOException {
if (file == null) {
throw MESSAGES.nullArgument("file");
}
if (paths == null) {
throw MESSAGES.nullArgument("paths");
}
boolean trace = VFSLogger.ROOT_LOGGER.isTraceEnabled();
Manifest manifest = getManifest(file);
if (manifest == null) { return; }
Attributes mainAttributes = manifest.getMainAttributes();
String classPath = mainAttributes.getValue(Attributes.Name.CLASS_PATH);
if (classPath == null) {
if (trace) {
VFSLogger.ROOT_LOGGER.tracef("Manifest has no Class-Path for %s", file.getPathName());
}
return;
}
VirtualFile parent = file.getParent();
if (parent == null) {
VFSLogger.ROOT_LOGGER.debugf("%s has no parent.", file);
return;
}
if (trace) {
VFSLogger.ROOT_LOGGER.tracef("Parsing Class-Path: %s for %s parent=%s", classPath, file.getName(), parent.getName());
}
StringTokenizer tokenizer = new StringTokenizer(classPath);
while (tokenizer.hasMoreTokens()) {
String path = tokenizer.nextToken();
try {
VirtualFile vf = parent.getChild(path);
if (vf.exists()) {
if (paths.contains(vf) == false) {
paths.add(vf);
// Recursively process the jar
Automounter.mount(file, vf);
addManifestLocations(vf, paths);
} else if (trace) {
VFSLogger.ROOT_LOGGER.tracef("%s from manifest is already in the classpath %s", vf.getName(), paths);
}
} else if (trace) {
VFSLogger.ROOT_LOGGER.trace("Unable to find " + path + " from " + parent.getName());
}
} catch (IOException e) {
VFSLogger.ROOT_LOGGER.debugf("Manifest Class-Path entry %s ignored for %s reason= %s", path, file.getPathName(), e);
}
}
}
/**
* Get a manifest from a virtual file, assuming the virtual file is the root of an archive
*
* @param archive the root the archive
* @return the manifest or null if not found
* @throws IOException if there is an error reading the manifest or the virtual file is closed
* @throws IllegalArgumentException for a null archive
*/
public static Manifest getManifest(VirtualFile archive) throws IOException {
if (archive == null) {
throw MESSAGES.nullArgument("archive");
}
VirtualFile manifest = archive.getChild(JarFile.MANIFEST_NAME);
if (manifest == null || !manifest.exists()) {
if (VFSLogger.ROOT_LOGGER.isTraceEnabled()) {
VFSLogger.ROOT_LOGGER.tracef("Can't find manifest for %s", archive.getPathName());
}
return null;
}
return readManifest(manifest);
}
/**
* Read the manifest from given manifest VirtualFile.
*
* @param manifest the VF to read from
* @return JAR's manifest
* @throws IOException if problems while opening VF stream occur
*/
public static Manifest readManifest(VirtualFile manifest) throws IOException {
if (manifest == null) {
throw MESSAGES.nullArgument("manifest file");
}
InputStream stream = new PaddedManifestStream(manifest.openStream());
try {
return new Manifest(stream);
} finally {
safeClose(stream);
}
}
/**
* Fix a name (removes any trailing slash)
*
* @param name the name to fix
* @return the fixed name
* @throws IllegalArgumentException for a null name
*/
public static String fixName(String name) {
if (name == null) {
throw MESSAGES.nullArgument("name");
}
int length = name.length();
if (length <= 1) { return name; }
if (name.charAt(length - 1) == '/') { return name.substring(0, length - 1); }
return name;
}
/**
* Decode the path with UTF-8 encoding..
*
* @param path the path to decode
* @return decoded path
*/
public static String decode(String path) {
return decode(path, DEFAULT_ENCODING);
}
/**
* Decode the path.
*
* @param path the path to decode
* @param encoding the encoding
* @return decoded path
*/
public static String decode(String path, String encoding) {
try {
return URLDecoder.decode(path, encoding);
} catch (UnsupportedEncodingException e) {
throw MESSAGES.cannotDecode(path,encoding,e);
}
}
/**
* Get the name.
*
* @param uri the uri
* @return name from uri's path
*/
public static String getName(URI uri) {
if (uri == null) {
throw MESSAGES.nullArgument("uri");
}
String name = uri.getPath();
if (name != null) {
// TODO: Not correct for certain uris like jar:...!/
int lastSlash = name.lastIndexOf('/');
if (lastSlash > 0) { name = name.substring(lastSlash + 1); }
}
return name;
}
/**
* Deal with urls that may include spaces.
*
* @param url the url
* @return uri the uri
* @throws URISyntaxException for any error
*/
public static URI toURI(URL url) throws URISyntaxException {
if (url == null) {
throw MESSAGES.nullArgument("url");
}
try {
return url.toURI();
} catch (URISyntaxException e) {
String urispec = url.toExternalForm();
// Escape percent sign and spaces
urispec = urispec.replaceAll("%", "%25");
urispec = urispec.replaceAll(" ", "%20");
return new URI(urispec);
}
}
/**
* Ensure the url is convertible to URI by encoding spaces and percent characters if necessary
*
* @param url to be sanitized
* @return sanitized URL
* @throws URISyntaxException if URI conversion can't be fixed
* @throws MalformedURLException if an error occurs
*/
public static URL sanitizeURL(URL url) throws URISyntaxException, MalformedURLException {
return toURI(url).toURL();
}
/**
* Copy all the children from the original {@link VirtualFile} the target recursively.
*
* @param original the file to copy children from
* @param target the file to copy the children to
* @throws IOException if any problems occur copying the files
*/
public static void copyChildrenRecursive(VirtualFile original, VirtualFile target) throws IOException {
if (original == null) {
throw MESSAGES.nullArgument("Original VirtualFile");
}
if (target == null) {
throw MESSAGES.nullArgument("Target VirtualFile");
}
List children = original.getChildren();
for (VirtualFile child : children) {
VirtualFile targetChild = target.getChild(child.getName());
File childFile = child.getPhysicalFile();
if (childFile.isDirectory()) {
if (!targetChild.getPhysicalFile().mkdir()) {
throw MESSAGES.problemCreatingNewDirectory(targetChild);
}
copyChildrenRecursive(child, targetChild);
} else {
FileInputStream is = new FileInputStream(childFile);
writeFile(targetChild, is);
}
}
}
/**
* Copy input stream to output stream and close them both
*
* @param is input stream
* @param os output stream
* @throws IOException for any error
*/
public static void copyStreamAndClose(InputStream is, OutputStream os) throws IOException {
copyStreamAndClose(is, os, DEFAULT_BUFFER_SIZE);
}
/**
* Copy input stream to output stream and close them both
*
* @param is input stream
* @param os output stream
* @param bufferSize the buffer size to use
* @throws IOException for any error
*/
public static void copyStreamAndClose(InputStream is, OutputStream os, int bufferSize)
throws IOException {
try {
copyStream(is, os, bufferSize);
// throw an exception if the close fails since some data might be lost
is.close();
os.close();
} finally {
// ...but still guarantee that they're both closed
safeClose(is);
safeClose(os);
}
}
/**
* Copy input stream to output stream without closing streams. Flushes output stream when done.
*
* @param is input stream
* @param os output stream
* @throws IOException for any error
*/
public static void copyStream(InputStream is, OutputStream os) throws IOException {
copyStream(is, os, DEFAULT_BUFFER_SIZE);
}
/**
* Copy input stream to output stream without closing streams. Flushes output stream when done.
*
* @param is input stream
* @param os output stream
* @param bufferSize the buffer size to use
* @throws IOException for any error
*/
public static void copyStream(InputStream is, OutputStream os, int bufferSize)
throws IOException {
if (is == null) {
throw MESSAGES.nullArgument("input stream");
}
if (os == null) {
throw MESSAGES.nullArgument("output stream");
}
byte[] buff = new byte[bufferSize];
int rc;
while ((rc = is.read(buff)) != -1) { os.write(buff, 0, rc); }
os.flush();
}
/**
* Write the given bytes to the given virtual file, replacing its current contents (if any) or creating a new file if
* one does not exist.
*
* @param virtualFile the virtual file to write
* @param bytes the bytes
* @throws IOException if an error occurs
*/
public static void writeFile(VirtualFile virtualFile, byte[] bytes) throws IOException {
final File file = virtualFile.getPhysicalFile();
file.getParentFile().mkdirs();
final FileOutputStream fos = new FileOutputStream(file);
try {
fos.write(bytes);
fos.close();
} finally {
safeClose(fos);
}
}
/**
* Write the content from the given {@link InputStream} to the given virtual file, replacing its current contents (if any) or creating a new file if
* one does not exist.
*
* @param virtualFile the virtual file to write
* @param is the input stream
* @throws IOException if an error occurs
*/
public static void writeFile(VirtualFile virtualFile, InputStream is) throws IOException {
final File file = virtualFile.getPhysicalFile();
file.getParentFile().mkdirs();
final FileOutputStream fos = new FileOutputStream(file);
copyStreamAndClose(is, fos);
}
/**
* Get the virtual URL for a virtual file. This URL can be used to access the virtual file; however, taking the file
* part of the URL and attempting to use it with the {@link java.io.File} class may fail if the file is not present
* on the physical filesystem, and in general should not be attempted.
* Note: if the given VirtualFile refers to a directory at the time of this
* method invocation, a trailing slash will be appended to the URL; this means that invoking
* this method may require a filesystem access, and in addition, may not produce consistent results
* over time.
*
* @param file the virtual file
* @return the URL
* @throws MalformedURLException if the file cannot be coerced into a URL for some reason
* @see VirtualFile#asDirectoryURL()
* @see VirtualFile#asFileURL()
*/
public static URL getVirtualURL(VirtualFile file) throws MalformedURLException {
try {
URI uri = getVirtualURI(file);
String scheme = uri.getScheme();
if (VFS_PROTOCOL.equals(scheme)) {
return new URL(null, uri.toString(), VFS_URL_HANDLER);
} else if ("file".equals(scheme)) {
return new URL(null, uri.toString(), FILE_URL_HANDLER);
} else {
return uri.toURL();
}
} catch (URISyntaxException e) {
throw new MalformedURLException(e.getMessage());
}
}
/**
* Get the virtual URI for a virtual file.
* Note: if the given VirtualFile refers to a directory at the time of this
* method invocation, a trailing slash will be appended to the URI; this means that invoking
* this method may require a filesystem access, and in addition, may not produce consistent results
* over time.
*
* @param file the virtual file
* @return the URI
* @throws URISyntaxException if the file cannot be coerced into a URI for some reason
* @see VirtualFile#asDirectoryURI()
* @see VirtualFile#asFileURI()
*/
public static URI getVirtualURI(VirtualFile file) throws URISyntaxException {
return new URI(VFS_PROTOCOL, "", file.getPathName(true), null);
}
/**
* Get a physical URL for a virtual file. See the warnings on the {@link VirtualFile#getPhysicalFile()} method
* before using this method.
*
* @param file the virtual file
* @return the physical file URL
* @throws IOException if an I/O error occurs getting the physical file
*/
public static URL getPhysicalURL(VirtualFile file) throws IOException {
return getPhysicalURI(file).toURL();
}
/**
* Get a physical URI for a virtual file. See the warnings on the {@link VirtualFile#getPhysicalFile()} method
* before using this method.
*
* @param file the virtual file
* @return the physical file URL
* @throws IOException if an I/O error occurs getting the physical file
*/
public static URI getPhysicalURI(VirtualFile file) throws IOException {
return file.getPhysicalFile().toURI();
}
/**
* Get the physical root URL of the filesystem of a virtual file. This URL is suitable for use as a class loader's
* code source or in similar situations where only standard URL types ({@code jar} and {@code file}) are supported.
*
* @param file the virtual file
* @return the root URL
* @throws MalformedURLException if the URL is not valid
*/
public static URL getRootURL(VirtualFile file) throws MalformedURLException {
final URI uri;
try {
uri = getRootURI(file);
} catch (URISyntaxException e) {
throw new MalformedURLException(e.getMessage());
}
return uri.toURL();
}
/**
* Get the physical root URL of the filesystem of a virtual file. This URI is suitable for conversion to a class loader's
* code source URL or in similar situations where only standard URL types ({@code jar} and {@code file}) are supported.
*
* @param file the virtual file
* @return the root URI
* @throws URISyntaxException if the URI is not valid
*/
public static URI getRootURI(final VirtualFile file) throws URISyntaxException {
return VFS.getMount(file).getFileSystem().getRootURI();
}
/**
* Safely close some resource without throwing an exception. Any exception will be logged at TRACE level.
*
* @param c the resource
*/
public static void safeClose(final Closeable c) {
if (c != null) {
try {
c.close();
} catch (Exception e) {
VFSLogger.ROOT_LOGGER.trace("Failed to close resource", e);
}
}
}
/**
* Safely close some resource without throwing an exception. Any exception will be logged at TRACE level.
*
* @param closeables the resources
*/
public static void safeClose(final Closeable... closeables) {
safeClose(Arrays.asList(closeables));
}
/**
* Safely close some resources without throwing an exception. Any exception will be logged at TRACE level.
*
* @param ci the resources
*/
public static void safeClose(final Iterable extends Closeable> ci) {
if (ci != null) {
for (Closeable closeable : ci) {
safeClose(closeable);
}
}
}
/**
* Safely close some resource without throwing an exception. Any exception will be logged at TRACE level.
*
* @param zipFile the resource
*/
public static void safeClose(final ZipFile zipFile) {
if (zipFile != null) {
try {
zipFile.close();
} catch (Exception e) {
VFSLogger.ROOT_LOGGER.trace("Failed to close resource", e);
}
}
}
/**
* Attempt to recursively delete a real file.
*
* @param root the real file to delete
* @return {@code true} if the file was deleted
*/
public static boolean recursiveDelete(File root) {
boolean ok = true;
if (root.isDirectory()) {
final File[] files = root.listFiles();
if (files != null) {
for (File file : files) {
ok &= recursiveDelete(file);
}
}
return ok && (root.delete() || !root.exists());
} else {
ok &= root.delete() || !root.exists();
}
return ok;
}
/**
* Attempt to recursively delete a virtual file.
*
* @param root the virtual file to delete
* @return {@code true} if the file was deleted
*/
public static boolean recursiveDelete(VirtualFile root) {
boolean ok = true;
if (root.isDirectory()) {
final List files = root.getChildren();
for (VirtualFile file : files) {
ok &= recursiveDelete(file);
}
return ok && (root.delete() || !root.exists());
} else {
ok &= root.delete() || !root.exists();
}
return ok;
}
/**
* Recursively copy a file or directory from one location to another.
*
* @param original the original file or directory
* @param destDir the destination directory
* @throws IOException if an I/O error occurs before the copy is complete
*/
public static void recursiveCopy(File original, File destDir) throws IOException {
final String name = original.getName();
final File destFile = new File(destDir, name);
if (original.isDirectory()) {
destFile.mkdir();
for (File file : original.listFiles()) {
recursiveCopy(file, destFile);
}
} else {
final OutputStream os = new FileOutputStream(destFile);
try {
final InputStream is = new FileInputStream(original);
copyStreamAndClose(is, os);
} finally {
// in case the input stream open fails
safeClose(os);
}
}
}
/**
* Recursively copy a file or directory from one location to another.
*
* @param original the original file or directory
* @param destDir the destination directory
* @throws IOException if an I/O error occurs before the copy is complete
*/
public static void recursiveCopy(File original, VirtualFile destDir) throws IOException {
final String name = original.getName();
final File destFile = destDir.getChild(name).getPhysicalFile();
if (original.isDirectory()) {
destFile.mkdir();
for (File file : original.listFiles()) {
recursiveCopy(file, destFile);
}
} else {
final OutputStream os = new FileOutputStream(destFile);
try {
final InputStream is = new FileInputStream(original);
copyStreamAndClose(is, os);
} finally {
// in case the input stream open fails
safeClose(os);
}
}
}
/**
* Recursively copy a file or directory from one location to another.
*
* @param original the original virtual file or directory
* @param destDir the destination directory
* @throws IOException if an I/O error occurs before the copy is complete
*/
public static void recursiveCopy(VirtualFile original, File destDir) throws IOException {
final String name = original.getName();
final File destFile = new File(destDir, name);
if (original.isDirectory()) {
destFile.mkdir();
for (VirtualFile file : original.getChildren()) {
recursiveCopy(file, destFile);
}
} else {
final OutputStream os = new FileOutputStream(destFile);
try {
final InputStream is = original.openStream();
copyStreamAndClose(is, os);
} finally {
// in case the input stream open fails
safeClose(os);
}
}
}
/**
* Recursively copy a file or directory from one location to another.
*
* @param original the original virtual file or directory
* @param destDir the destination virtual directory
* @throws IOException if an I/O error occurs before the copy is complete
*/
public static void recursiveCopy(VirtualFile original, VirtualFile destDir) throws IOException {
final String name = original.getName();
final File destFile = destDir.getChild(name).getPhysicalFile();
if (original.isDirectory()) {
destFile.mkdir();
for (VirtualFile file : original.getChildren()) {
recursiveCopy(file, destFile);
}
} else {
final OutputStream os = new FileOutputStream(destFile);
try {
final InputStream is = original.openStream();
copyStreamAndClose(is, os);
} finally {
// in case the input stream open fails
safeClose(os);
}
}
}
private static final InputStream EMPTY_STREAM = new InputStream() {
public int read() throws IOException {
return -1;
}
};
/**
* Get the empty input stream. This stream always reports an immediate EOF.
*
* @return the empty input stream
*/
public static InputStream emptyStream() {
return EMPTY_STREAM;
}
/**
* Get an input stream that will always be consumable as a Zip/Jar file. The input stream will not be an instance
* of a JarInputStream, but will stream bytes according to the Zip specification. Using this method, a VFS file
* or directory can be written to disk as a normal jar/zip file.
*
* @param virtualFile The virtual to get a jar file input stream for
* @return An input stream returning bytes according to the zip spec
* @throws IOException if any problems occur
*/
public static InputStream createJarFileInputStream(final VirtualFile virtualFile) throws IOException {
if (virtualFile.isDirectory()) {
final VirtualJarInputStream jarInputStream = new VirtualJarInputStream(virtualFile);
return new VirtualJarFileInputStream(jarInputStream);
}
InputStream inputStream = null;
try {
final byte[] expectedHeader = new byte[4];
expectedHeader[0] = (byte) (JarEntry.LOCSIG & 0xff);
expectedHeader[1] = (byte) ((JarEntry.LOCSIG >>> 8) & 0xff);
expectedHeader[2] = (byte) ((JarEntry.LOCSIG >>> 16) & 0xff);
expectedHeader[3] = (byte) ((JarEntry.LOCSIG >>> 24) & 0xff);
inputStream = virtualFile.openStream();
final byte[] bytes = new byte[4];
final int read = inputStream.read(bytes, 0, 4);
if (read < 4 || !Arrays.equals(expectedHeader, bytes)) {
throw MESSAGES.invalidJarSignature(Arrays.toString(bytes), Arrays.toString(expectedHeader));
}
} finally {
safeClose(inputStream);
}
return virtualFile.openStream();
}
/**
* Expand a zip file to a destination directory. The directory must exist. If an error occurs, the destination
* directory may contain a partially-extracted archive, so cleanup is up to the caller.
*
* @param zipFile the zip file
* @param destDir the destination directory
* @throws IOException if an error occurs
*/
public static void unzip(File zipFile, File destDir) throws IOException {
final ZipFile zip = new ZipFile(zipFile);
try {
final Set createdDirs = new HashSet();
final Enumeration extends ZipEntry> entries = zip.entries();
FILES_LOOP:
while (entries.hasMoreElements()) {
final ZipEntry zipEntry = entries.nextElement();
final String name = zipEntry.getName();
final List tokens = PathTokenizer.getTokens(name);
final Iterator it = tokens.iterator();
File current = destDir;
while (it.hasNext()) {
String token = it.next();
if (PathTokenizer.isCurrentToken(token) || PathTokenizer.isReverseToken(token)) {
// invalid file; skip it!
continue FILES_LOOP;
}
current = new File(current, token);
if ((it.hasNext() || zipEntry.isDirectory()) && createdDirs.add(current)) {
current.mkdir();
}
}
if (!zipEntry.isDirectory()) {
final InputStream is = zip.getInputStream(zipEntry);
try {
final FileOutputStream os = new FileOutputStream(current);
try {
VFSUtils.copyStream(is, os);
// allow an error on close to terminate the unzip
is.close();
os.close();
} finally {
VFSUtils.safeClose(os);
}
} finally {
VFSUtils.safeClose(is);
}
current.setLastModified(zipEntry.getTime());
}
}
} finally {
VFSUtils.safeClose(zip);
}
}
/**
* Return the mount source File for a given mount handle.
*
* @param handle The handle to get the source for
* @return The mount source file or null if the handle does not have a source, or is not a MountHandle
*/
public static File getMountSource(Closeable handle) {
if (handle instanceof MountHandle) { return MountHandle.class.cast(handle).getMountSource(); }
return null;
}
private static final Pattern GLOB_PATTERN = Pattern.compile("(\\*\\*?)|(\\?)|(\\\\.)|(/+)|([^*?]+)");
/**
* Get a regular expression pattern which matches any path names which match the given glob. The glob patterns
* function similarly to {@code ant} file patterns. Valid meta-characters in the glob pattern include:
*
*
"\" - escape the next character (treat it literally, even if it is itself a recognized meta-character)
*
"?" - match any non-slash character
*
"*" - match zero or more non-slash characters
*
"**" - match zero or more characters, including slashes
*
"/" - match one or more slash characters. Consecutive {@code /} characters are collapsed down into one.
*
* In addition, like {@code ant}, if the pattern ends with a {@code /}, then an implicit "**" will be appended.
*
* See also:"Patterns" in the Ant Manual
*
* @param glob the glob to match
* @return the pattern
*/
public static Pattern getGlobPattern(final String glob) {
StringBuilder patternBuilder = new StringBuilder();
patternBuilder.append("^");
final Matcher m = GLOB_PATTERN.matcher(glob);
boolean lastWasSlash = false;
while (m.find()) {
lastWasSlash = false;
String grp;
if ((grp = m.group(1)) != null) {
// match a * or **
if (grp.length() == 2) {
// it's a **
patternBuilder.append(".*");
} else {
// it's a *
patternBuilder.append("[^/]*");
}
} else if ((grp = m.group(2)) != null) {
// match a '?' glob pattern; any non-slash character
patternBuilder.append("[^/]");
} else if ((grp = m.group(3)) != null) {
// backslash-escaped value
patternBuilder.append(grp.charAt(1));
} else if ((grp = m.group(4)) != null) {
// match any number of / chars
patternBuilder.append("/+");
lastWasSlash = true;
} else {
// some other string
patternBuilder.append(Pattern.quote(m.group()));
}
}
if (lastWasSlash) {
// ends in /, append **
patternBuilder.append(".*");
}
patternBuilder.append("$");
return Pattern.compile(patternBuilder.toString());
}
/**
* Canonicalize the given path. Removes all {@code .} and {@code ..} segments from the path.
*
* @param path the relative or absolute possibly non-canonical path
* @return the canonical path
*/
@SuppressWarnings("UnusedLabel") // for documentation
public static String canonicalize(final String path) {
final int length = path.length();
// 0 - start
// 1 - got one .
// 2 - got two .
// 3 - got /
int state = 0;
if (length == 0) {
return path;
}
final char[] targetBuf = new char[length];
// string segment end exclusive
int e = length;
// string cursor position
int i = length;
// buffer cursor position
int a = length - 1;
// number of segments to skip
int skip = 0;
loop:
while (--i >= 0) {
char c = path.charAt(i);
outer:
switch (c) {
case '/': {
inner:
switch (state) {
case 0:
state = 3;
e = i;
break outer;
case 1:
state = 3;
e = i;
break outer;
case 2:
state = 3;
e = i;
skip++;
break outer;
case 3:
e = i;
break outer;
default:
throw new IllegalStateException();
}
// not reached!
}
case '.': {
inner:
switch (state) {
case 0:
state = 1;
break outer;
case 1:
state = 2;
break outer;
case 2:
break inner; // emit!
case 3:
state = 1;
break outer;
default:
throw new IllegalStateException();
}
// fall thru
}
default: {
final int newE = e > 0 ? path.lastIndexOf('/', e - 1) : -1;
final int segmentLength = e - newE - 1;
if (skip > 0) {
skip--;
} else {
if (state == 3) {
targetBuf[a--] = '/';
}
path.getChars(newE + 1, e, targetBuf, (a -= segmentLength) + 1);
}
state = 0;
i = newE + 1;
e = newE;
break;
}
}
}
if (state == 3) {
targetBuf[a--] = '/';
}
return new String(targetBuf, a + 1, length - a - 1);
}
}