org.jruby.util.JarResource Maven / Gradle / Ivy
package org.jruby.util;
import jnr.posix.FileStat;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.file.attribute.FileTime;
import java.util.jar.JarEntry;
abstract class JarResource implements FileResource, DummyResourceStat.FileResourceExt {
private static final JarCache jarCache = new JarCache();
public static JarResource create(String pathname) {
int bang = pathname.indexOf('!');
if (bang == -1) return null; // no ! no jar!
if (pathname.startsWith("jar:")) {
if (pathname.startsWith("file:", 4)) {
pathname = pathname.substring(9); bang -= 9; // 4 + 5
}
else {
pathname = pathname.substring(4); bang -= 4;
}
}
else if (pathname.startsWith("file:")) {
pathname = pathname.substring(5); bang -= 5;
}
String jarPath = pathname.substring(0, bang);
String entryPath = pathname.substring(bang + 1);
// normalize path -- issue #2017
if (StringSupport.startsWith(entryPath, '/', '/')) entryPath = entryPath.substring(1);
// TODO: Do we really need to support both test.jar!foo/bar.rb and test.jar!/foo/bar.rb cases?
JarResource resource = createJarResource(jarPath, entryPath, false);
if (resource == null && StringSupport.startsWith(entryPath, '/')) {
resource = createJarResource(jarPath, entryPath.substring(1), true);
}
return resource;
}
private static JarResource createJarResource(String jarPath, String entryPath, boolean rootSlashPrefix) {
JarCache.JarIndex index = jarCache.getIndex(jarPath);
if (index == null) { // Jar doesn't exist
try {
jarPath = URLDecoder.decode(jarPath, "UTF-8");
entryPath = URLDecoder.decode(entryPath, "UTF-8");
}
catch (IllegalArgumentException e) {
// something in the path did not decode, so it's probably not a URI
// See jruby/jruby#2264.
return null;
}
catch (UnsupportedEncodingException e) {
throw new AssertionError(e);
}
index = jarCache.getIndex(jarPath);
if (index == null) return null; // Jar doesn't exist
}
// Try it as directory first, because jars tend to have foo/ entries
// and it's not really possible disambiguate between files and directories.
String[] entries = index.getDirEntries(entryPath);
if (entries != null) {
return new JarDirectoryResource(jarPath, rootSlashPrefix, entryPath, entries);
}
if (entryPath.length() > 1 && entryPath.endsWith("/")) { // in case 'foo/' passed
entries = index.getDirEntries(entryPath.substring(0, entryPath.length() - 1));
if (entries != null) {
return new JarDirectoryResource(jarPath, rootSlashPrefix, entryPath, entries);
}
}
JarEntry jarEntry = index.getJarEntry(entryPath);
if (jarEntry != null) {
return new JarFileResource(jarPath, rootSlashPrefix, index, jarEntry);
}
return null;
}
public static boolean removeJarResource(String jarPath){
return jarCache.remove(jarPath);
}
private final CharSequence jarPrefix;
JarResource(String jarPath, boolean rootSlashPrefix) {
StringBuilder prefix = new StringBuilder(jarPath.length() + 2);
prefix.append(jarPath).append('!');
this.jarPrefix = rootSlashPrefix ? prefix.append('/') : prefix;
}
private transient String absolutePath;
@Override
public final String absolutePath() {
String path = this.absolutePath;
if (path != null) return path;
return this.absolutePath = jarPrefix + entryName();
}
@Override
public String canonicalPath() {
return absolutePath();
}
@Override
public boolean exists() {
// If a jar resource got created, then it always corresponds to some kind of resource
return true;
}
@Override
public boolean canRead() {
// Can always read from a jar
return true;
}
@Override
public boolean canWrite() {
return false;
}
@Override
public boolean canExecute() {
return false;
}
@Override
public boolean isSymLink() {
// Jar archives shouldn't contain symbolic links, or it would break portability. `jar`
// command behavior seems to comform to that (it unwraps syumbolic links when creating a jar
// and replaces symbolic links with regular file when extracting from a zip that contains
// symbolic links). Also see:
// http://www.linuxquestions.org/questions/linux-general-1/how-to-create-jar-files-with-symbolic-links-639381/
return false;
}
private transient FileStat fileStat;
@Override
public FileStat stat() {
FileStat fileStat = this.fileStat;
if (fileStat != null) return fileStat;
return this.fileStat = new DummyResourceStat(this);
}
@Override
public FileStat lstat() {
return stat(); // jars don't have symbolic links, so lstat == stat
}
@Override
public int errno() {
return 0; // stat() never fails
}
@Override
public boolean isNull() {
return false;
}
public abstract FileTime creationTime() throws IOException;
public abstract FileTime lastAccessTime() throws IOException;
public abstract FileTime lastModifiedTime() throws IOException;
@Override
public long lastModified() {
FileTime mod = null;
try {
mod = lastModifiedTime();
}
catch (IOException ex) { /* -1 invalid? */ }
return mod == null ? 0L : mod.toMillis();
}
abstract String entryName();
@Override
public String toString() {
return getClass().getName() + '{' + absolutePath() + '}';
}
@Override
public boolean equals(Object obj) {
if (obj instanceof JarResource) {
JarResource that = (JarResource) obj;
return this.absolutePath().equals(that.absolutePath());
}
return false;
}
@Override
public int hashCode() {
return 11 * entryName().hashCode();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy