-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Custom ClassLoader for indexing classes #85
Changes from 8 commits
67ea64a
e36e5a8
5e6fb89
f385baa
a95af1e
ab5a243
a825719
fb3d67d
465cde0
4436022
720e80c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,35 +14,52 @@ | |
*/ | ||
package org.walkmod.javalang.compiler.types; | ||
|
||
import java.util.HashMap; | ||
import java.util.Map; | ||
import java.util.*; | ||
|
||
import org.walkmod.javalang.ast.SymbolData; | ||
import org.walkmod.javalang.ast.type.Type; | ||
import org.walkmod.javalang.compiler.symbols.ASTSymbolTypeResolver; | ||
import org.walkmod.javalang.compiler.symbols.SymbolType; | ||
|
||
public class SymbolTypesClassLoader extends ClassLoader { | ||
public class CachedClassLoader extends ClassLoader { | ||
|
||
public static Map<String, Class<?>> PRIMITIVES = new HashMap<String, Class<?>>(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Collections.unmodifyableMap? |
||
|
||
static { | ||
// static block to resolve primitive classes | ||
PRIMITIVES.put("boolean", boolean.class); | ||
PRIMITIVES.put("int", int.class); | ||
PRIMITIVES.put("long", long.class); | ||
PRIMITIVES.put("double", double.class); | ||
PRIMITIVES.put("char", char.class); | ||
PRIMITIVES.put("float", float.class); | ||
PRIMITIVES.put("short", short.class); | ||
PRIMITIVES.put("byte", byte.class); | ||
PRIMITIVES.put("void", void.class); | ||
} | ||
|
||
private Map<String, Class<?>> cache = new HashMap<String, Class<?>>(); | ||
|
||
public SymbolTypesClassLoader(ClassLoader parent) { | ||
public CachedClassLoader(IndexedURLClassLoader parent) { | ||
super(parent); | ||
cache.put("boolean", boolean.class); | ||
cache.put("int", int.class); | ||
cache.put("long", long.class); | ||
cache.put("double", double.class); | ||
cache.put("char", char.class); | ||
cache.put("float", float.class); | ||
cache.put("short", short.class); | ||
cache.put("byte", byte.class); | ||
cache.put("void", void.class); | ||
|
||
cache.putAll(PRIMITIVES); | ||
|
||
} | ||
|
||
public Class<?> loadClass(Type t) throws ClassNotFoundException { | ||
|
||
return ASTSymbolTypeResolver.getInstance().valueOf(t).getClazz(); | ||
} | ||
|
||
public Class<?> loadClass(SymbolType t) throws ClassNotFoundException { | ||
|
||
public List<String> getPackageContents(String packageName) { | ||
return ((IndexedURLClassLoader)getParent()).getPackageClasses(packageName); | ||
} | ||
|
||
public List<String> getSDKContents(String packageName) { | ||
return ((IndexedURLClassLoader)getParent()).getSDKContents(packageName); | ||
} | ||
|
||
public Class<?> loadClass(SymbolData t) throws ClassNotFoundException { | ||
String name = t.getName(); | ||
Class<?> cachedClass = cache.get(name); | ||
if (cachedClass == null) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
package org.walkmod.javalang.compiler.types; | ||
|
||
import java.io.ByteArrayOutputStream; | ||
import java.io.File; | ||
import java.io.IOException; | ||
import java.io.InputStream; | ||
import java.net.MalformedURLException; | ||
import java.net.URL; | ||
import java.security.AllPermission; | ||
import java.security.CodeSource; | ||
import java.security.PermissionCollection; | ||
import java.security.Permissions; | ||
import java.security.ProtectionDomain; | ||
import java.security.cert.Certificate; | ||
import java.util.List; | ||
|
||
|
||
/** | ||
* A custom ClassLoader that indexes the contents of classpath elements, for faster class locating. | ||
* | ||
* The standard URLClassLoader does a linear scan of the classpath for each class or resource, which | ||
* becomes prohibitively expensive for classpaths with many elements. | ||
*/ | ||
public class IndexedURLClassLoader extends ClassLoader { | ||
|
||
public IndexedURLClassLoader(ClassLoader parent) { | ||
// parent is the default system classloader, which we want to bypass entirely in | ||
// the delegation hierarchy, so we make our parent that thing's parent instead. | ||
super(parent.getParent()); | ||
this.ucp = new IndexedURLClassPath(getClassPathURLs()); | ||
} | ||
|
||
public URL[] getURLs() { | ||
return this.ucp.getURLs(); | ||
} | ||
|
||
public IndexedURLClassLoader(URL[] urls, ClassLoader parent) { | ||
super(parent); | ||
this.ucp = new IndexedURLClassPath(urls); | ||
} | ||
|
||
public List<String> getPackageClasses(String packageName) { | ||
return ucp.listPackageContents(packageName); | ||
} | ||
|
||
public List<String> getSDKContents(String packageName) { | ||
return ucp.listSDKContents(packageName); | ||
} | ||
|
||
private static URL[] getClassPathURLs() { | ||
try { | ||
String[] paths = System.getProperties().getProperty("java.class.path").split(File.pathSeparator); | ||
URL[] urls = new URL[paths.length]; | ||
for (int i = 0; i < paths.length; ++i) { | ||
urls[i] = new File(paths[i]).toURI().toURL(); | ||
} | ||
return urls; | ||
} catch (MalformedURLException e) { | ||
throw new RuntimeException(e); | ||
} | ||
} | ||
|
||
/* The search path for classes and resources */ | ||
private IndexedURLClassPath ucp; | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Style: Move to top of class? |
||
@Override | ||
public URL findResource(String name) { | ||
return ucp.findResource(name); | ||
} | ||
|
||
@Override | ||
protected Class<?> findClass(String name) throws ClassNotFoundException { | ||
try { | ||
String path = name.replace('.', '/').concat(".class"); | ||
URL res = ucp.findResource(path); | ||
if (res != null) { | ||
int i = name.lastIndexOf('.'); | ||
if (i != -1) { | ||
String pkgname = name.substring(0, i); | ||
// Check if package already loaded. | ||
Package pkg = getPackage(pkgname); | ||
if (pkg == null) { | ||
definePackage(pkgname, null, null, null, null, null, null, null); | ||
} | ||
} | ||
byte[] data = loadResource(res); | ||
// Add a CodeSource via a ProtectionDomain, as code may use this to find its own jars. | ||
CodeSource cs = new CodeSource(res, (Certificate[])null); | ||
PermissionCollection pc = new Permissions(); | ||
pc.add(new AllPermission()); | ||
ProtectionDomain pd = new ProtectionDomain(cs, pc); | ||
return defineClass(name, data, 0, data.length, pd); | ||
} else { | ||
throw new ClassNotFoundException(String.format("IndexedURLClassLoader failed to read class %s", name)); | ||
} | ||
} catch (IOException e) { | ||
throw new ClassNotFoundException(String.format("IndexedURLClassLoader failed to read class %s", name), e); | ||
} | ||
} | ||
|
||
private byte[] loadResource(URL toDownload) throws IOException { | ||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); | ||
|
||
byte[] chunk = new byte[4096]; | ||
int bytesRead; | ||
InputStream stream = toDownload.openStream(); | ||
try { | ||
while ((bytesRead = stream.read(chunk)) > 0) { | ||
outputStream.write(chunk, 0, bytesRead); | ||
} | ||
} finally { | ||
stream.close(); | ||
} | ||
return outputStream.toByteArray(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
package org.walkmod.javalang.compiler.types; | ||
|
||
import java.io.File; | ||
import java.io.IOException; | ||
import java.net.MalformedURLException; | ||
import java.net.URL; | ||
import java.util.*; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wildcard? |
||
import java.util.jar.JarEntry; | ||
import java.util.jar.JarFile; | ||
|
||
/** | ||
* A modified URLClassPath that indexes the contents of classpath elements, for faster resource locating. | ||
* | ||
* The standard URLClassPath does a linear scan of the classpath for each resource, which becomes | ||
* prohibitively expensive for classpaths with many elements. | ||
*/ | ||
public class IndexedURLClassPath { | ||
|
||
private final URL[] urls; | ||
private int lastIndexed = 0; | ||
private static URL RT_JAR; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Final? |
||
// Map from resource name to URLClassPath to delegate loading that resource to. | ||
private final PathTree<URL> index = new PathTree<URL>(); | ||
|
||
static { | ||
// static block to resolve java.lang package classes | ||
String[] bootPath = System.getProperties().get("sun.boot.class.path").toString() | ||
.split(Character.toString(File.pathSeparatorChar)); | ||
for (String lib : bootPath) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. File.pathSeparator |
||
if (lib.endsWith("rt.jar")) { | ||
File f = new File(lib); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will that work for java 9? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, I would care if rt is not part of java 9 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The internal files rt.jar, tools.jar, and dt.jar have been removed. The content of these files is now stored in a more efficient format in implementation-private files in the lib directory. Class and resource files previously in tools.jar and dt.jar are now always visible via the bootstrap or application class loaders in a JDK image. 😱 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am going to create a Issue, because it is a different discussion. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
try { | ||
RT_JAR = f.toURI().toURL(); | ||
} catch (MalformedURLException e) { | ||
throw new RuntimeException("The java.lang classes cannot be loaded", e.getCause()); | ||
} | ||
} | ||
} | ||
} | ||
|
||
public IndexedURLClassPath(final URL[] urls) { | ||
this.urls = urls; | ||
} | ||
|
||
|
||
public URL findResource(final String name) { | ||
URL delegate = index.get(name); | ||
if (delegate == null) { | ||
if (lastIndexed < urls.length) { | ||
indexURLs(urls[lastIndexed]); | ||
lastIndexed ++; | ||
return findResource(name); | ||
} | ||
return null; | ||
} | ||
try { | ||
return new URL(delegate, name); | ||
} catch (MalformedURLException e) { | ||
throw new RuntimeException(e); | ||
} | ||
} | ||
|
||
|
||
public List<String> listPackageContents(final String packageName) { | ||
|
||
String packageFile = packageName.replaceAll("\\.", File.separator); | ||
while (lastIndexed < urls.length) { | ||
indexURLs(urls[lastIndexed]); | ||
lastIndexed ++; | ||
} | ||
return index.list(packageFile); | ||
} | ||
|
||
public List<String> listSDKContents(final String packageName) { | ||
String packageFile = packageName.replaceAll("\\.", File.separator); | ||
indexURLs(RT_JAR); | ||
return index.list(packageFile); | ||
} | ||
|
||
|
||
private void indexURLs(URL url) { | ||
try { | ||
if (!"file".equals(url.getProtocol())) { | ||
throw new RuntimeException("Classpath element is not a file: " + url); | ||
} | ||
File root = new File(url.getPath()); | ||
|
||
if (root.isDirectory()) { | ||
String rootPath = root.getPath(); | ||
if (!rootPath.endsWith(File.separator)) { | ||
rootPath = rootPath + File.separator; | ||
} | ||
addFilesToIndex(rootPath.length(), root, url); | ||
} else if (root.isFile() && root.getName().endsWith(".jar")) { | ||
JarFile jarFile = new JarFile(root); | ||
try { | ||
Enumeration<JarEntry> entries = jarFile.entries(); | ||
while (entries.hasMoreElements()) { | ||
JarEntry entry = entries.nextElement(); | ||
String name = entry.getName(); | ||
maybeIndexResource(name, url); | ||
} | ||
} finally { | ||
jarFile.close(); | ||
} | ||
} | ||
} catch (IOException e) { | ||
throw new RuntimeException(e); | ||
} | ||
} | ||
|
||
private void addFilesToIndex(int basePrefixLen, File f, URL delegate) throws IOException { | ||
if (f.isDirectory()) { | ||
if (f.getPath().length() > basePrefixLen) { // Don't index the root itself. | ||
String relPath = f.getPath().substring(basePrefixLen); | ||
maybeIndexResource(relPath, delegate); | ||
} | ||
File[] directoryEntries = f.listFiles(); | ||
|
||
if (directoryEntries == null) { | ||
throw new RuntimeException("The list of directories of " + f.getAbsolutePath() + " is null"); | ||
} | ||
for (int i = 0; i < directoryEntries.length; ++i) { | ||
addFilesToIndex(basePrefixLen, directoryEntries[i], delegate); | ||
} | ||
} else { | ||
String relPath = f.getPath().substring(basePrefixLen); | ||
maybeIndexResource(relPath, delegate); | ||
} | ||
} | ||
|
||
public URL[] getURLs() { | ||
return urls; | ||
} | ||
|
||
/** | ||
* Callers may request the directory itself as a resource, and may | ||
* do so with or without trailing slashes. We do this in a while-loop | ||
* in case the classpath element has multiple superfluous trailing slashes. | ||
* @param relPath relative path | ||
* @param delegate value to insert | ||
*/ | ||
private void maybeIndexResource(String relPath, URL delegate) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What I meant was:
And using the same strip-Method for lookup method of index, too. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Theoretically, you could have multiple classes from different jars under the same package and the JDK uses the first that appears in the classpath. |
||
|
||
if (!index.containsKey(relPath)) { | ||
index.put(relPath, delegate); | ||
if (relPath.endsWith(File.separator)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Move before outer if to store only without trailing slashes. Strip trailingvslashes on lookup, too if not already done. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
maybeIndexResource(relPath.substring(0, relPath.length() - File.separator.length()), delegate); | ||
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Style: wild or no wild?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It has been automatically performed by Intellij. I will removed it.