blob: 90e878e75342873393e0ea7bbb3fb3032565794b [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2007 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<JarCLInputStream>();
private boolean _open = true;
public JarClassLoader(List<File> jarFiles, final ClassLoader parent) {
super(parent);
// Handle manifest classpath entries
_files = new LinkedHashSet<File>(jarFiles);
for (File f : jarFiles) {
_recursiveGetManifestJars(f, _files);
}
open();
}
private void open() {
// Create all jar files
_jars = new ArrayList<JarFile>(_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) {}
}
}
}
}