- /* *******************************************************************
- * Copyright (c) 2002, 2017 Contributors
- * All rights reserved.
- * This program and the accompanying materials are made available
- * under the terms of the Eclipse Public License v 2.0
- * which accompanies this distribution and is available at
- * https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.txt
- *
- * Contributors:
- * Palo Alto Research Center, Incorporated (PARC).
- * ******************************************************************/
- package org.aspectj.weaver.bcel;
-
- import java.io.ByteArrayInputStream;
- import java.io.File;
- import java.io.FileInputStream;
- import java.io.FileNotFoundException;
- import java.io.IOException;
- import java.io.InputStream;
- import java.net.MalformedURLException;
- import java.net.URI;
- import java.net.URL;
- import java.net.URLClassLoader;
- import java.nio.file.FileSystem;
- import java.nio.file.FileSystems;
- import java.nio.file.FileVisitResult;
- import java.nio.file.Files;
- import java.nio.file.Path;
- import java.nio.file.SimpleFileVisitor;
- import java.nio.file.attribute.BasicFileAttributes;
- import java.util.ArrayList;
- import java.util.Enumeration;
- import java.util.HashMap;
- import java.util.HashSet;
- import java.util.Iterator;
- import java.util.List;
- import java.util.Map;
- import java.util.Set;
- import java.util.zip.ZipEntry;
- import java.util.zip.ZipFile;
-
- import org.aspectj.bridge.IMessageHandler;
- import org.aspectj.bridge.MessageUtil;
- import org.aspectj.util.LangUtil;
- import org.aspectj.util.SoftHashMap;
- import org.aspectj.weaver.BCException;
- import org.aspectj.weaver.UnresolvedType;
- import org.aspectj.weaver.WeaverMessages;
- import org.aspectj.weaver.tools.Trace;
- import org.aspectj.weaver.tools.TraceFactory;
-
- /**
- * @author Andy Clement
- * @author Mario Ivankovits
- */
- public class ClassPathManager {
-
- private static Trace trace = TraceFactory.getTraceFactory().getTrace(ClassPathManager.class);
-
- private static int maxOpenArchives = -1;
-
- private static URI JRT_URI = URI.create("jrt:/"); //$NON-NLS-1$
-
- private static final int MAXOPEN_DEFAULT = 1000;
-
- private final List<Entry> entries;
-
- private final Set<String> notFound = new HashSet<>(100);
-
- // In order to control how many open files we have, we maintain a list.
- // The max number is configured through the property:
- // org.aspectj.weaver.openarchives
- // and it defaults to 1000
- private final List<ZipFile> openArchives = new ArrayList<>();
-
- static {
- String openzipsString = getSystemPropertyWithoutSecurityException("org.aspectj.weaver.openarchives",
- Integer.toString(MAXOPEN_DEFAULT));
- maxOpenArchives = Integer.parseInt(openzipsString);
- if (maxOpenArchives < 20) {
- maxOpenArchives = MAXOPEN_DEFAULT;
- }
- }
-
- public ClassPathManager(List<String> classpath, IMessageHandler handler) {
- if (trace.isTraceEnabled()) {
- trace.enter("<init>", this, new Object[] { classpath==null?"null":classpath.toString(), handler });
- }
- entries = new ArrayList<>(classpath == null ? 1 : classpath.size());
- for (String classpathEntry: classpath) {
- addPath(classpathEntry,handler);
- }
- if (trace.isTraceEnabled()) {
- trace.exit("<init>");
- }
- }
-
- protected ClassPathManager() {
- entries = null;
- }
-
- public void addPath(String name, IMessageHandler handler) {
- File f = new File(name);
- if (!f.isDirectory()) {
- if (!f.isFile()) {
- if (!name.toLowerCase().endsWith(".jar") || name.toLowerCase().endsWith(".zip")) {
- // heuristic-only: ending with .jar or .zip means probably a zip file
- MessageUtil.info(handler, WeaverMessages.format(WeaverMessages.ZIPFILE_ENTRY_MISSING, name));
- } else {
- MessageUtil.info(handler, WeaverMessages.format(WeaverMessages.DIRECTORY_ENTRY_MISSING, name));
- }
- return;
- }
- try {
- if (name.toLowerCase().endsWith(LangUtil.JRT_FS)) { // Java9+
- entries.add(new JImageEntry(name));
- } else {
- entries.add(new ZipFileEntry(f));
- }
- } catch (IOException ioe) {
- MessageUtil.warn(handler,
- WeaverMessages.format(WeaverMessages.ZIPFILE_ENTRY_INVALID, name, ioe.getMessage()));
- return;
- }
- } else {
- entries.add(new DirEntry(f));
- }
- }
-
- public ClassFile find(UnresolvedType type) {
- if (trace.isTraceEnabled()) {
- trace.enter("find", this, type);
- }
- String name = type.getName();
- if (notFound.contains(name)) {
- return null;
- }
- for (Iterator<Entry> i = entries.iterator(); i.hasNext();) {
- Entry entry = i.next();
- try {
- ClassFile ret = entry.find(name);
- if (trace.isTraceEnabled()) {
- trace.event("searching for "+type+" in "+entry.toString());
- }
- if (ret != null) {
- if (trace.isTraceEnabled()) {
- trace.exit("find", ret);
- }
- return ret;
- }
- } catch (IOException ioe) {
- // this is NOT an error: it's valid to have missing classpath entries
- if (trace.isTraceEnabled()) {
- trace.error("Removing classpath entry for "+entry,ioe);
- }
- i.remove();
- }
- }
- if (trace.isTraceEnabled()) {
- trace.exit("find", null);
- }
- notFound.add(name);
- return null;
- }
-
- @Override
- public String toString() {
- StringBuilder buf = new StringBuilder();
- boolean start = true;
- for (Entry entry : entries) {
- if (start) {
- start = false;
- } else {
- buf.append(File.pathSeparator);
- }
- buf.append(entry);
- }
- return buf.toString();
- }
-
- public abstract static class ClassFile {
- public abstract InputStream getInputStream() throws IOException;
- public abstract String getPath();
- public abstract void close();
- }
-
- abstract static class Entry {
- public abstract ClassFile find(String name) throws IOException;
- }
-
- static class ByteBasedClassFile extends ClassFile {
-
- private final byte[] bytes;
- private ByteArrayInputStream bais;
- private final String path;
-
- public ByteBasedClassFile(byte[] bytes, String path) {
- this.bytes = bytes;
- this.path = path;
- }
-
- @Override
- public InputStream getInputStream() throws IOException {
- this.bais = new ByteArrayInputStream(bytes);
- return this.bais;
- }
-
- @Override
- public String getPath() {
- return this.path;
- }
-
- @Override
- public void close() {
- if (this.bais!=null) {
- try {
- this.bais.close();
- } catch (IOException e) {
- }
- this.bais = null;
- }
- }
-
- }
-
- static class FileClassFile extends ClassFile {
- private final File file;
- private FileInputStream fis;
-
- public FileClassFile(File file) {
- this.file = file;
- }
-
- @Override
- public InputStream getInputStream() throws IOException {
- fis = new FileInputStream(file);
- return fis;
- }
-
- @Override
- public void close() {
- try {
- if (fis != null)
- fis.close();
- } catch (IOException ioe) {
- throw new BCException("Can't close class file : " + file.getName(), ioe);
- } finally {
- fis = null;
- }
- }
-
- @Override
- public String getPath() {
- return file.getPath();
- }
- }
-
- class DirEntry extends Entry {
- private final String dirPath;
-
- public DirEntry(File dir) {
- this.dirPath = dir.getPath();
- }
-
- public DirEntry(String dirPath) {
- this.dirPath = dirPath;
- }
-
- @Override
- public ClassFile find(String name) {
- File f = new File(dirPath + File.separator + name.replace('.', File.separatorChar) + ".class");
- if (f.isFile())
- return new FileClassFile(f);
- else
- return null;
- }
-
- @Override
- public String toString() {
- return dirPath;
- }
- }
-
- static class ZipEntryClassFile extends ClassFile {
- private final ZipEntry entry;
- private final ZipFileEntry zipFile;
- private InputStream is;
-
- public ZipEntryClassFile(ZipFileEntry zipFile, ZipEntry entry) {
- this.zipFile = zipFile;
- this.entry = entry;
- }
-
- @Override
- public InputStream getInputStream() throws IOException {
- is = zipFile.getZipFile().getInputStream(entry);
- return is;
- }
-
- @Override
- public void close() {
- try {
- if (is != null)
- is.close();
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- is = null;
- }
- }
-
- @Override
- public String getPath() {
- return entry.getName();
- }
-
- }
-
- /**
- * Maintains a shared package cache for java runtime image. This maps packages (for example:
- * java/lang) to a starting root position in the filesystem (for example: /modules/java.base/java/lang).
- * When searching for a type we work out the package name, use it to find where in the filesystem
- * to start looking then run from there. Once found we do cache what we learn to make subsequent
- * lookups of that type even faster. Maintaining just a package cache rather than complete type cache
- * helps reduce memory usage but still gives reasonably fast lookup performance.
- */
- static class JImageEntry extends Entry {
-
- // Map from a JRT-FS file to the cache state for that file
- private static Map<String, JImageState> states = new HashMap<>();
-
- private JImageState state;
-
- // TODO memory management here - is it held onto too long when LTW?
- static class JImageState {
- private final String jrtFsPath;
- private final FileSystem fs;
- Map<String,Path> fileCache = new SoftHashMap<>();
- boolean packageCacheInitialized = false;
- Map<String,Path> packageCache = new HashMap<>();
-
- public JImageState(String jrtFsPath, FileSystem fs) {
- this.jrtFsPath = jrtFsPath;
- this.fs = fs;
- }
- }
-
- public JImageEntry(String jrtFsPath) {
- state = states.get(jrtFsPath);
- if (state == null) {
- synchronized (states) {
- if (state == null) {
- URL jrtPath = null;
- try {
- jrtPath = new File(jrtFsPath).toPath().toUri().toURL();
- } catch (MalformedURLException e) {
- System.out.println("Unexpected problem processing "+jrtFsPath+" bad classpath entry? skipping:"+e.getMessage());
- return;
- }
- String jdkHome = new File(jrtFsPath).getParentFile().getParent();
- FileSystem fs = null;
- try {
- if (LangUtil.isVMGreaterOrEqual(9)) {
- Map<String, String> env = new HashMap<>();
- env.put("java.home", jdkHome);
- fs = FileSystems.newFileSystem(JRT_URI, env);
- } else {
- URLClassLoader loader = new URLClassLoader(new URL[] { jrtPath });
- Map<String, ?> env = new HashMap<>();
- fs = FileSystems.newFileSystem(JRT_URI, env, loader);
- }
- state = new JImageState(jrtFsPath, fs);
- states.put(jrtFsPath, state);
- buildPackageMap();
- } catch (Throwable t) {
- throw new IllegalStateException("Unexpectedly unable to initialize a JRT filesystem", t);
- }
- }
- }
- }
- }
-
- class PackageCacheBuilderVisitor extends SimpleFileVisitor<Path> {
- @Override
- public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
- if (file.getNameCount() > 3 && file.toString().endsWith(".class")) {
- int fnc = file.getNameCount();
- if (fnc > 3) { // There is a package name - e.g. /modules/java.base/java/lang/Object.class
- Path packagePath = file.subpath(2, fnc-1); // e.g. java/lang
- String packagePathString = packagePath.toString();
- state.packageCache.put(packagePathString, file.subpath(0, fnc-1)); // java/lang -> /modules/java.base/java/lang
- }
- }
- return FileVisitResult.CONTINUE;
- }
- }
-
- /**
- * Create a map from package names to the specific directory of the package members in the filesystem.
- */
- private synchronized void buildPackageMap() {
- if (!state.packageCacheInitialized) {
- state.packageCacheInitialized = true;
- Iterable<java.nio.file.Path> roots = state.fs.getRootDirectories();
- PackageCacheBuilderVisitor visitor = new PackageCacheBuilderVisitor();
- try {
- for (java.nio.file.Path path : roots) {
- Files.walkFileTree(path, visitor);
- }
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
- }
-
- class TypeIdentifier extends SimpleFileVisitor<Path> {
-
- // What are we looking for?
- private final String name;
-
- // If set, where did we find it?
- public Path found;
-
- // Basic metric count of how many files we checked before finding it
- public int filesSearchedCount;
-
- public TypeIdentifier(String name) {
- this.name = name;
- }
-
- @Override
- public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
- filesSearchedCount++;
- if (file.getNameCount() > 2 && file.toString().endsWith(".class")) {
- int fnc = file.getNameCount();
- Path filePath = file.subpath(2, fnc);
- String filePathString = filePath.toString();
- if (filePathString.equals(name)) {
- state.fileCache.put(filePathString, file);
- found = file;
- return FileVisitResult.TERMINATE;
- }
- }
- return FileVisitResult.CONTINUE;
- }
- }
-
- private Path searchForFileAndCache(final Path startPath, final String name) {
- TypeIdentifier locator = new TypeIdentifier(name);
- try {
- Files.walkFileTree(startPath, locator);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- return locator.found;
- }
-
- @Override
- public ClassFile find(String name) throws IOException {
- String fileName = name.replace('.', '/') + ".class";
- Path file = state.fileCache.get(fileName);
- if (file == null) {
- // Check the packages map to see if we know about this package
- int idx = fileName.lastIndexOf('/');
- if (idx == -1) {
- // Package not here
- return null;
- }
- Path packageStart = null;
- String packageName = null;
- if (idx !=-1 ) {
- packageName = fileName.substring(0, idx);
- packageStart = state.packageCache.get(packageName);
- if (packageStart != null) {
- file = searchForFileAndCache(packageStart, fileName);
- }
- }
- }
- if (file == null) {
- return null;
- }
- byte[] bs = Files.readAllBytes(file);
- ClassFile cf = new ByteBasedClassFile(bs, fileName);
- return cf;
- }
-
- Map<String, Path> getPackageCache() {
- return state.packageCache;
- }
-
- Map<String, Path> getFileCache() {
- return state.fileCache;
- }
-
- }
-
- class ZipFileEntry extends Entry {
- private File file;
- private ZipFile zipFile;
-
- public ZipFileEntry(File file) throws IOException {
- this.file = file;
- }
-
- public ZipFileEntry(ZipFile zipFile) {
- this.zipFile = zipFile;
- }
-
- public ZipFile getZipFile() {
- return zipFile;
- }
-
- @Override
- public ClassFile find(String name) throws IOException {
- ensureOpen();
- String key = name.replace('.', '/') + ".class";
- ZipEntry entry = zipFile.getEntry(key);
- if (entry != null)
- return new ZipEntryClassFile(this, entry);
- else
- return null; // This zip will be closed when necessary...
- }
-
- public List<ZipEntryClassFile> getAllClassFiles() throws IOException {
- ensureOpen();
- List<ZipEntryClassFile> ret = new ArrayList<>();
- for (Enumeration<? extends ZipEntry> e = zipFile.entries(); e.hasMoreElements();) {
- ZipEntry entry = e.nextElement();
- String name = entry.getName();
- if (hasClassExtension(name))
- ret.add(new ZipEntryClassFile(this, entry));
- }
- // if (ret.isEmpty()) close();
- return ret;
- }
-
- private void ensureOpen() throws IOException {
- if (zipFile != null && openArchives.contains(zipFile)) {
- if (isReallyOpen())
- return;
- }
- if (openArchives.size() >= maxOpenArchives) {
- closeSomeArchives(openArchives.size() / 10); // Close 10% of
- // those open
- }
- zipFile = new ZipFile(file);
- if (!isReallyOpen()) {
- throw new FileNotFoundException("Can't open archive: " + file.getName() + " (size() check failed)");
- }
- openArchives.add(zipFile);
- }
-
- private boolean isReallyOpen() {
- try {
- zipFile.size(); // this will fail if the file has been closed
- // for
- // some reason;
- return true;
- } catch (IllegalStateException ex) {
- // this means the zip file is closed...
- return false;
- }
-
- }
-
- public void closeSomeArchives(int n) {
- for (int i = n - 1; i >= 0; i--) {
- ZipFile zf = openArchives.get(i);
- try {
- zf.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- openArchives.remove(i);
- }
- }
-
- public void close() {
- if (zipFile == null)
- return;
- try {
- openArchives.remove(zipFile);
- zipFile.close();
- } catch (IOException ioe) {
- throw new BCException("Can't close archive: " + file.getName(), ioe);
- } finally {
- zipFile = null;
- }
- }
-
- @Override
- public String toString() {
- return file.getName();
- }
- }
-
- /* private */static boolean hasClassExtension(String name) {
- return name.toLowerCase().endsWith(".class");
- }
-
- public void closeArchives() {
- for (Entry entry : entries) {
- if (entry instanceof ZipFileEntry) {
- ((ZipFileEntry) entry).close();
- }
- openArchives.clear();
- }
- }
-
- // Copes with the security manager
- private static String getSystemPropertyWithoutSecurityException(String aPropertyName, String aDefaultValue) {
- try {
- return System.getProperty(aPropertyName, aDefaultValue);
- } catch (SecurityException ex) {
- return aDefaultValue;
- }
- }
-
- // Mainly exposed for testing
- public List<Entry> getEntries() {
- return entries;
- }
-
- }
|