/******************************************************************************* * Copyright (c) 2005, 2015 BEA Systems, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * jgarms@bea.com - initial API and implementation * *******************************************************************************/ package org.eclipse.jdt.apt.core.internal; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.ArrayList; import java.util.Enumeration; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.jar.Attributes; import java.util.jar.JarFile; import java.util.jar.Manifest; import java.util.zip.ZipEntry; /** * This classloader allows us to close out underlying jars, * so that projects can be deleted even if they contain * factory jars that are in use.<P> * * This classloader caches open jars while it is in use, * and once closed it will close those jars. It can still be used * after that point, but it will open and close on each classloader * operation. */ public class JarClassLoader extends ClassLoader { // This is nulled out when the classloader is closed private List<JarFile> _jars; private final LinkedHashSet<File> _files; private List<JarCLInputStream> _openStreams = new LinkedList<>(); private boolean _open = true; public JarClassLoader(List<File> jarFiles, final ClassLoader parent) { super(parent); // Handle manifest classpath entries _files = new LinkedHashSet<>(jarFiles); for (File f : jarFiles) { _recursiveGetManifestJars(f, _files); } open(); } private void open() { // Create all jar files _jars = new ArrayList<>(_files.size()); for (File f : _files) { try { JarFile jar = new JarFile(f); _jars.add(jar); } catch (IOException ioe) { AptPlugin.log(ioe, "Unable to create JarFile for file: " + f); //$NON-NLS-1$ } } } public synchronized void close() { if (! _open) return; _open = false; for (JarCLInputStream st : _openStreams) { try { st.close(); } catch (IOException ioe) { AptPlugin.log(ioe, "Failed to close stream"); //$NON-NLS-1$ } } _openStreams = null; for (JarFile jar : _jars) { try { jar.close(); } catch (IOException ioe) { AptPlugin.log(ioe, "Failed to close jar: " + jar); //$NON-NLS-1$ } } _jars = null; } private InputStream openInputStream(InputStream in) { JarCLInputStream result = new JarCLInputStream(in); _openStreams.add(result); return in; } private synchronized void closeInputStream(JarCLInputStream in) { if (_open) _openStreams.remove(in); } @Override protected synchronized Class<?> findClass(String name) throws ClassNotFoundException { if (!_open) throw new ClassNotFoundException("Classloader closed: " + name); //$NON-NLS-1$ byte[] b = loadClassData(name); if (b == null) { throw new ClassNotFoundException("Could not find class " + name); //$NON-NLS-1$ } Class<?> clazz = defineClass(name, b, 0, b.length); // Define the package if necessary String pkgName = getPackageName(name); if (pkgName != null) { Package pkg = getPackage(pkgName); if (pkg == null) { definePackage(pkgName, null, null, null, null, null, null, null); } } return clazz; } private String getPackageName(String fullyQualifiedName) { int index = fullyQualifiedName.lastIndexOf('.'); if (index != -1) { return fullyQualifiedName.substring(0, index); } return null; } // returns null if no class found private byte[] loadClassData(String name) { name = name.replace('.','/'); InputStream input = getResourceAsStream(name + ".class"); //$NON-NLS-1$ if (input == null) return null; try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] buf = new byte[1024]; int len; while ((len = input.read(buf)) > 0) { baos.write(buf, 0, len); } baos.close(); return baos.toByteArray(); } catch (IOException ioe) { return null; } finally { try {input.close();} catch (IOException ioe) {} } } @Override public synchronized InputStream getResourceAsStream(String name) { InputStream input = getParent().getResourceAsStream(name); if (input != null) return input; if (!_open) return null; for (JarFile j : _jars) { try { ZipEntry entry = j.getEntry(name); if (entry != null) { InputStream zipInput = j.getInputStream(entry); return openInputStream(zipInput); } } catch (IOException ioe) { AptPlugin.log(ioe, "Unable to get entry from jar: " + j); //$NON-NLS-1$ } } return null; } /** * This is difficult to implement and close out resources underneath. * Delaying until someone actually requests this. * * If we actually contain the entry throw UnsupportedOperationException, * else return null in case another classloader can handle this. */ @Override public URL getResource(String name) { for (JarFile j : _jars) { ZipEntry entry = j.getEntry(name); if (entry != null) { throw new UnsupportedOperationException("getResource() not implemented: " + name); //$NON-NLS-1$ } } return null; } /** * This is difficult to implement and close out resources underneath. * Delaying until someone actually requests this. */ @Override public Enumeration<URL> getResources(String name) throws IOException { throw new UnsupportedOperationException("getResources() not implemented"); //$NON-NLS-1$ } private class JarCLInputStream extends InputStream { private boolean _closed = false; private final InputStream _input; public JarCLInputStream(InputStream origInput) { _input = origInput; } @Override public void close() throws IOException { if (_closed) { // NOOP return; } try { super.close(); _input.close(); _closed = true; } finally { closeInputStream(this); } } @Override public int read() throws IOException { return _input.read(); } @Override public int available() throws IOException { return _input.available(); } @Override public synchronized void mark(int readlimit) { _input.mark(readlimit); } @Override public boolean markSupported() { return _input.markSupported(); } @Override public int read(byte[] b, int off, int len) throws IOException { return _input.read(b, off, len); } @Override public int read(byte[] b) throws IOException { return _input.read(b); } @Override public synchronized void reset() throws IOException { _input.reset(); } @Override public long skip(long n) throws IOException { return _input.skip(n); } } /** * Scan manifest classpath entries of a jar, adding all found jar files * to the set of manifest jars. */ private static void _recursiveGetManifestJars(File jarFile, Set<File> manifestJars) { if (!jarFile.exists()) return; JarFile jar = null; try { jar = new JarFile(jarFile); Manifest mf = jar.getManifest(); if (mf == null) return; String classpath = mf.getMainAttributes().getValue(Attributes.Name.CLASS_PATH); if (classpath == null) return; // We've got some entries File parent = jarFile.getParentFile(); String[] rgPaths = classpath.split(" "); //$NON-NLS-1$ for (String path : rgPaths) { if (path.length() == 0) continue; File file = new File(parent, path); // If we haven't seen this, we need to get its manifest jars as well if (!manifestJars.contains(file) && file.exists()) { manifestJars.add(file); _recursiveGetManifestJars(file, manifestJars); } } } catch (IOException ioe) { } finally { if (jar != null) { try {jar.close();} catch (IOException ioe) {} } } } }