Mirror of the JODE repository
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
jode/jode/jode/bytecode/SearchPath.java

581 lines
17 KiB

/* SearchPath Copyright (C) 1998-2002 Jochen Hoenicke.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program 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. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; see the file COPYING.LESSER. If not, write to
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Id$
*/
package jode.bytecode;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.StringTokenizer;
import java.util.Vector;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
import jode.GlobalOptions;
/**
* This class represents a path of multiple directories and/or zip files,
* where we can search for file names.
*
* @author Jochen Hoenicke
*/
public class SearchPath {
/**
* We need a different pathSeparatorChar, since ':' (used for most
* UNIX System) is used a protocol separator in URLs.
*
* We currently allow both pathSeparatorChar and
* altPathSeparatorChar and decide if it is a protocol separator
* by context.
*/
public static final char altPathSeparatorChar = ',';
URL[] bases;
byte[][] urlzips;
File[] dirs;
ZipFile[] zips;
String[] zipDirs;
Hashtable[] zipEntries;
private static void addEntry(Hashtable entries, String name) {
String dir = "";
int pathsep = name.lastIndexOf("/");
if (pathsep != -1) {
dir = name.substring(0, pathsep);
name = name.substring(pathsep+1);
}
Vector dirContent = (Vector) entries.get(dir);
if (dirContent == null) {
dirContent = new Vector();
entries.put(dir, dirContent);
if (dir != "")
addEntry(entries, dir);
}
dirContent.addElement(name);
}
private void fillZipEntries(int nr) {
Enumeration zipEnum = zips[nr].entries();
zipEntries[nr] = new Hashtable();
while (zipEnum.hasMoreElements()) {
ZipEntry ze = (ZipEntry) zipEnum.nextElement();
String name = ze.getName();
// if (name.charAt(0) == '/')
// name = name.substring(1);
if (zipDirs[nr] != null) {
if (!name.startsWith(zipDirs[nr]))
continue;
name = name.substring(zipDirs[nr].length());
}
if (!ze.isDirectory() && name.endsWith(".class"))
addEntry(zipEntries[nr], name);
}
}
private void readURLZip(int nr, URLConnection conn) {
int length = conn.getContentLength();
if (length <= 0)
// Give a approximation if length is unknown
length = 10240;
else
// Increase the length by one, so we hopefully don't need
// to grow the array later (we need a little overshot to
// know when the end is reached).
length++;
urlzips[nr] = new byte[length];
try {
InputStream is = conn.getInputStream();
int pos = 0;
for (;;) {
// This is ugly, is.available() may return zero even
// if there are more bytes.
int avail = Math.max(is.available(), 1);
if (pos + is.available() > urlzips[nr].length) {
// grow the byte array.
byte[] newarr = new byte
[Math.max(2*urlzips[nr].length, pos + is.available())];
System.arraycopy(urlzips[nr], 0, newarr, 0, pos);
urlzips[nr] = newarr;
}
int count = is.read(urlzips[nr], pos, urlzips[nr].length-pos);
if (count == -1)
break;
pos += count;
}
if (pos < urlzips[nr].length) {
// shrink the byte array again.
byte[] newarr = new byte[pos];
System.arraycopy(urlzips[nr], 0, newarr, 0, pos);
urlzips[nr] = newarr;
}
} catch (IOException ex) {
GlobalOptions.err.println("IOException while reading "
+"remote zip file "+bases[nr]);
// disable entry
bases[nr] = null;
urlzips[nr] = null;
return;
}
try {
// fill entries into hash table
ZipInputStream zis = new ZipInputStream
(new ByteArrayInputStream(urlzips[nr]));
zipEntries[nr] = new Hashtable();
ZipEntry ze;
while ((ze = zis.getNextEntry()) != null) {
String name = ze.getName();
// if (name.charAt(0) == '/')
// name = name.substring(1);
if (zipDirs[nr] != null) {
if (!name.startsWith(zipDirs[nr]))
continue;
name = name.substring(zipDirs[nr].length());
}
if (!ze.isDirectory() && name.endsWith(".class"))
addEntry(zipEntries[nr], name);
zis.closeEntry();
}
zis.close();
} catch (IOException ex) {
GlobalOptions.err.println("Remote zip file "+bases[nr]
+" is corrupted.");
// disable entry
bases[nr] = null;
urlzips[nr] = null;
zipEntries[nr] = null;
return;
}
}
/**
* Creates a new search path for the given path.
* @param path The path where we should search for files. They
* should be separated by the system dependent pathSeparator. The
* entries may also be zip or jar files.
*/
public SearchPath(String path) {
// Calculate a good approximation (rounded upwards) of the tokens
// in this path.
int length = 1;
for (int index=path.indexOf(File.pathSeparatorChar);
index != -1; length++)
index = path.indexOf(File.pathSeparatorChar, index+1);
if (File.pathSeparatorChar != altPathSeparatorChar) {
for (int index=path.indexOf(altPathSeparatorChar);
index != -1; length++)
index = path.indexOf(altPathSeparatorChar, index+1);
}
bases = new URL[length];
urlzips = new byte[length][];
dirs = new File[length];
zips = new ZipFile[length];
zipEntries = new Hashtable[length];
zipDirs = new String[length];
int i = 0;
for (int ptr=0; ptr < path.length(); ptr++, i++) {
int next = ptr;
while (next < path.length()
&& path.charAt(next) != File.pathSeparatorChar
&& path.charAt(next) != altPathSeparatorChar)
next++;
int index = ptr;
colon_separator:
while (next > ptr
&& next < path.length()
&& path.charAt(next) == ':') {
// Check if this is a URL instead of a pathSeparator
// Since this is a while loop it allows nested urls like
// jar:ftp://ftp.foo.org/pub/foo.jar!/
while (index < next) {
char c = path.charAt(index);
// According to RFC 1738 letters, digits, '+', '-'
// and '.' are allowed SCHEMA characters. We
// disallow '.' because it is a good marker that
// the user has specified a filename instead of a
// URL.
if ((c < 'A' || c > 'Z')
&& (c < 'a' || c > 'z')
&& (c < '0' || c > '9')
&& "+-".indexOf(c) == -1) {
break colon_separator;
}
index++;
}
next++;
index++;
while (next < path.length()
&& path.charAt(next) != File.pathSeparatorChar
&& path.charAt(next) != altPathSeparatorChar)
next++;
}
String token = path.substring(ptr, next);
ptr = next;
boolean mustBeJar = false;
// We handle jar URL's ourself.
if (token.startsWith("jar:")) {
index = 0;
do {
index = token.indexOf('!', index);
} while (index != -1 && index != token.length()-1
&& token.charAt(index+1) != '/');
if (index == -1 || index == token.length()-1) {
GlobalOptions.err.println("Warning: Illegal jar url "
+ token + ".");
continue;
}
zipDirs[i] = token.substring(index+2);
if (!zipDirs[i].endsWith("/"))
zipDirs[i] = zipDirs[i] + "/";
token = token.substring(4, index);
mustBeJar = true;
}
index = token.indexOf(':');
if (index != -1 && index < token.length()-2
&& token.charAt(index+1) == '/'
&& token.charAt(index+2) == '/') {
// This looks like an URL.
try {
bases[i] = new URL(token);
try {
URLConnection connection = bases[i].openConnection();
if (mustBeJar
|| token.endsWith(".zip") || token.endsWith(".jar")
|| connection.getContentType().endsWith("/zip")) {
// This is a zip file. Read it into memory.
readURLZip(i, connection);
}
} catch (IOException ex) {
// ignore
} catch (SecurityException ex) {
GlobalOptions.err.println
("Warning: Security exception while accessing "
+ bases[i] + ".");
}
} catch (MalformedURLException ex) {
/* disable entry */
bases[i] = null;
dirs[i] = null;
}
} else {
try {
dirs[i] = new File(token);
if (mustBeJar || !dirs[i].isDirectory()) {
try {
zips[i] = new ZipFile(dirs[i]);
} catch (java.io.IOException ex) {
/* disable this entry */
dirs[i] = null;
}
}
} catch (SecurityException ex) {
/* disable this entry */
GlobalOptions.err.println
("Warning: SecurityException while accessing "
+ token + ".");
dirs[i] = null;
}
}
}
}
public boolean exists(String filename) {
String localFileName =
(java.io.File.separatorChar != '/')
? filename.replace('/', java.io.File.separatorChar)
: filename;
for (int i=0; i<dirs.length; i++) {
if (zipEntries[i] != null) {
if (zipEntries[i].get(filename) != null)
return true;
String dir = "";
String name = filename;
int index = filename.lastIndexOf('/');
if (index >= 0) {
dir = filename.substring(0, index);
name = filename.substring(index+1);
}
Vector directory = (Vector)zipEntries[i].get(dir);
if (directory != null && directory.contains(name))
return true;
continue;
}
if (bases[i] != null) {
try {
URL url = new URL(bases[i], filename);
URLConnection conn = url.openConnection();
conn.connect();
conn.getInputStream().close();
return true;
} catch (IOException ex) {
/* ignore */
}
continue;
}
if (dirs[i] == null)
continue;
if (zips[i] != null) {
String fullname = zipDirs[i] != null
? zipDirs[i] + filename : filename;
ZipEntry ze = zips[i].getEntry(fullname);
if (ze != null)
return true;
} else {
try {
File f = new File(dirs[i], localFileName);
if (f.exists())
return true;
} catch (SecurityException ex) {
/* ignore and take next element */
}
}
}
return false;
}
/**
* Searches for a file in the search path.
* @param filename the filename. The path components should be separated
* by <code>/</code>.
* @return An InputStream for the file.
*/
public InputStream getFile(String filename) throws IOException {
String localFileName =
(java.io.File.separatorChar != '/')
? filename.replace('/', java.io.File.separatorChar)
: filename;
for (int i=0; i<dirs.length; i++) {
if (urlzips[i] != null) {
ZipInputStream zis = new ZipInputStream
(new ByteArrayInputStream(urlzips[i]));
ZipEntry ze;
String fullname = zipDirs[i] != null
? zipDirs[i] + filename : filename;
while ((ze = zis.getNextEntry()) != null) {
if (ze.getName().equals(fullname)) {
///#ifdef JDK11
/// // The skip method in jdk1.1.7 ZipInputStream
/// // is buggy. We return a wrapper that fixes
/// // this.
/// return new FilterInputStream(zis) {
/// private byte[] tmpbuf = new byte[512];
/// public long skip(long n) throws IOException {
/// long skipped = 0;
/// while (n > 0) {
/// int count = read(tmpbuf, 0,
/// (int)Math.min(n, 512L));
/// if (count == -1)
/// return skipped;
/// skipped += count;
/// n -= count;
/// }
/// return skipped;
/// }
/// };
///#else
return zis;
///#endif
}
zis.closeEntry();
}
continue;
}
if (bases[i] != null) {
try {
URL url = new URL(bases[i], filename);
URLConnection conn = url.openConnection();
conn.setAllowUserInteraction(true);
return conn.getInputStream();
} catch (SecurityException ex) {
GlobalOptions.err.println("Warning: SecurityException"
+ " while accessing "
+ bases[i] + filename);
ex.printStackTrace(GlobalOptions.err);
/* ignore and take next element */
} catch (FileNotFoundException ex) {
/* ignore and take next element */
}
continue;
}
if (dirs[i] == null)
continue;
if (zips[i] != null) {
String fullname = zipDirs[i] != null
? zipDirs[i] + filename : filename;
ZipEntry ze = zips[i].getEntry(fullname);
if (ze != null)
return zips[i].getInputStream(ze);
} else {
try {
File f = new File(dirs[i], localFileName);
if (f.exists())
return new FileInputStream(f);
} catch (SecurityException ex) {
GlobalOptions.err.println("Warning: SecurityException"
+ " while accessing "
+ dirs[i] + localFileName);
/* ignore and take next element */
}
}
}
throw new FileNotFoundException(filename);
}
/**
* Searches for a filename in the search path and tells if it is a
* directory.
* @param filename the filename. The path components should be separated
* by <code>/</code>.
* @return true, if filename exists and is a directory, false otherwise.
*/
public boolean isDirectory(String filename) {
String localFileName =
(java.io.File.separatorChar != '/')
? filename.replace('/', java.io.File.separatorChar)
: filename;
for (int i=0; i<dirs.length; i++) {
if (dirs[i] == null)
continue;
if (zips[i] != null && zipEntries[i] == null)
fillZipEntries(i);
if (zipEntries[i] != null) {
if (zipEntries[i].containsKey(filename))
return true;
} else {
try {
File f = new File(dirs[i], localFileName);
if (f.exists())
return f.isDirectory();
} catch (SecurityException ex) {
GlobalOptions.err.println("Warning: SecurityException"
+ " while accessing "
+ dirs[i] + localFileName);
}
}
}
return false;
}
/**
* Searches for all files in the given directory.
* @param dirName the directory name. The path components should
* be separated by <code>/</code>.
* @return An enumeration with all files/directories in the given
* directory. */
public Enumeration listFiles(final String dirName) {
return new Enumeration() {
int pathNr;
Enumeration zipEnum;
int fileNr;
String localDirName =
(java.io.File.separatorChar != '/')
? dirName.replace('/', java.io.File.separatorChar)
: dirName;
File currentDir;
String[] files;
public String findNextFile() {
while (true) {
if (zipEnum != null) {
while (zipEnum.hasMoreElements()) {
return (String) zipEnum.nextElement();
}
zipEnum = null;
}
if (files != null) {
while (fileNr < files.length) {
String name = files[fileNr++];
if (name.endsWith(".class")) {
return name;
} else if (name.indexOf(".") == -1) {
/* ignore directories containing a dot.
* they can't be a package directory.
*/
File f = new File(currentDir, name);
if (f.exists() && f.isDirectory())
return name;
}
}
files = null;
}
if (pathNr == dirs.length)
return null;
if (zips[pathNr] != null && zipEntries[pathNr] == null)
fillZipEntries(pathNr);
if (zipEntries[pathNr] != null) {
Vector entries =
(Vector) zipEntries[pathNr].get(dirName);
if (entries != null)
zipEnum = entries.elements();
} else if (dirs[pathNr] != null) {
try {
File f = new File(dirs[pathNr], localDirName);
if (f.exists() && f.isDirectory()) {
currentDir = f;
files = f.list();
fileNr = 0;
}
} catch (SecurityException ex) {
GlobalOptions.err.println
("Warning: SecurityException"
+ " while accessing "
+ dirs[pathNr] + localDirName);
/* ignore and take next element */
}
}
pathNr++;
}
}
String nextName;
public boolean hasMoreElements() {
return (nextName != null
|| (nextName = findNextFile()) != null);
}
public Object nextElement() {
if (nextName == null)
return findNextFile();
else {
String result = nextName;
nextName = null;
return result;
}
}
};
}
}