/** * Copyright 2011 Adrian Witas * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.abstractmeta.toolbox.compilation.compiler.impl; import org.abstractmeta.toolbox.compilation.compiler.registry.JavaFileObjectRegistry; import org.abstractmeta.toolbox.compilation.compiler.util.URIUtil; import com.google.common.io.ByteStreams; import com.google.common.io.Closeables; import com.google.common.io.Files; import javax.tools.JavaFileObject; import javax.tools.StandardLocation; import java.io.*; import java.net.MalformedURLException; import java.net.URI; import java.net.URL; import java.util.*; import java.util.jar.JarEntry; import java.util.jar.JarFile; /** * <p>This implementation uses the following mechanism to lookup requested class. * <ul> * <li>java object registry: {@link org.abstractmeta.toolbox.compilation.compiler.registry.JavaFileObjectRegistry}</li> * <li>jar files class path entries</li> * <li>directory class path entries</li> * </ul> * Note that to be able find use {@link SimpleClassLoader#findResource(String)} or {@link SimpleClassLoader#findResources(String)} for registry resources (classes compiled in memory), * please use {@link JavaSourceCompilerImpl#persistCompiledClasses(org.abstractmeta.toolbox.compilation.compiler.JavaSourceCompiler.CompilationUnit)} * to persist compiled classes. * </p> * * @author Adrian Witas */ public class SimpleClassLoader extends ClassLoader { private final List<JarFile> jarFiles = new ArrayList<JarFile>(); private final List<File> classDirectories = new ArrayList<File>(); private final JavaFileObjectRegistry registry; private final File classOutputDirectory; public SimpleClassLoader(final ClassLoader parentClassLoader, JavaFileObjectRegistry registry, File classOutputDirectory) { super(parentClassLoader); this.registry = registry; this.classOutputDirectory = classOutputDirectory; } public void addClassPathEntry(String classPathEntry) { if (classPathEntry.endsWith(".jar")) { try { JarFile jarFile = new JarFile(classPathEntry); jarFiles.add(jarFile); } catch (IOException e) { throw new IllegalStateException("Failed to register classPath entry: " + classPathEntry, e); } } else { File classDirectory = new File(classPathEntry); classDirectories.add(classDirectory); } } public void addClassPathEntries(Iterable<String> classPathEntries) { for (String classPathEntry : classPathEntries) { addClassPathEntry(classPathEntry); } } @Override protected Class<?> findClass(final String qualifiedClassName) throws ClassNotFoundException { URI classUri = URIUtil.buildUri(StandardLocation.CLASS_OUTPUT, qualifiedClassName); if (registry.isRegistered(classUri)) { JavaFileObject result = registry.get(classUri); byte[] byteCode = JavaCodeFileObject.class.cast(result).getByteCode(); return defineClass(qualifiedClassName, byteCode, 0, byteCode.length); } Class<?> result = findClassInFileSystem(qualifiedClassName); if (result != null) { return result; } result = findClassInJarFile(qualifiedClassName); if (result != null) { return result; } try { result = Class.forName(qualifiedClassName); return result; } catch (ClassNotFoundException nf) { // Ignore and fall through } return super.findClass(qualifiedClassName); } protected Class<?> findClassInFileSystem(String qualifiedClassName) { for (File classDirectory : classDirectories) { File classFile = new File(classDirectory, qualifiedClassName.replace('.', '/') + ".class"); if (classFile.exists()) { try { byte[] byteCode = Files.toByteArray(classFile); return defineClass(qualifiedClassName, byteCode, 0, byteCode.length); } catch (IOException e) { throw new IllegalStateException("Failed to read class file " + classFile, e); } } } return null; } protected Class<?> findClassInJarFile(String qualifiedClassName) throws ClassNotFoundException { URI classUri = URIUtil.buildUri(StandardLocation.CLASS_OUTPUT, qualifiedClassName); String internalClassName = classUri.getPath().substring(1); JarFile jarFile = null; try { for (int i = 0; i < jarFiles.size(); i++) { jarFile = jarFiles.get(i); JarEntry jarEntry = jarFile.getJarEntry(internalClassName); if (jarEntry != null) { InputStream inputStream = jarFile.getInputStream(jarEntry); try { byte[] byteCode = new byte[(int) jarEntry.getSize()]; ByteStreams.read(inputStream, byteCode, 0, byteCode.length); return defineClass(qualifiedClassName, byteCode, 0, byteCode.length); } finally { Closeables.closeQuietly(inputStream); } } } } catch (IOException e) { throw new IllegalStateException(String.format("Failed to lookup class %s in jar file %s", qualifiedClassName, jarFile), e); } return null; } @Override protected Enumeration<URL> findResources(String resource) throws IOException { List<URL> result = new ArrayList<URL>(Collections.list(super.findResources(resource))); findResourcesInJarFiles(result, resource); findResourcesInJavaFileObjectRegistry(result, resource); return Collections.enumeration(result); } protected void findResourcesInJarFiles(List<URL> result, String resource) throws MalformedURLException { for (JarFile jarFile : jarFiles) { JarEntry entry = jarFile.getJarEntry(resource); if (entry != null) { result.add(new URL("jar", "", String.format("file:%s!%s", jarFile.getName(), resource))); } } } protected void findResourcesInJavaFileObjectRegistry(List<URL> result, String resource) throws MalformedURLException { for (JavaFileObject javaFileObject : registry.get(JavaFileObject.Kind.CLASS)) { String internalName = javaFileObject.getName().substring(1); if (internalName.startsWith(resource)) { File file = new File(classOutputDirectory, internalName); result.add(new URL(String.format("file://%s", file.getAbsolutePath()))); } } } }