Skip to content
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

Merged
merged 11 commits into from
Apr 17, 2018
Original file line number Diff line number Diff line change
Expand Up @@ -14,35 +14,52 @@
*/
package org.walkmod.javalang.compiler.types;

import java.util.HashMap;
import java.util.Map;
import java.util.*;
Copy link
Collaborator

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?

Copy link
Owner Author

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.


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<?>>();
Copy link
Collaborator

Choose a reason for hiding this comment

The 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) {
Expand Down
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;

Copy link
Collaborator

Choose a reason for hiding this comment

The 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.*;
Copy link
Collaborator

Choose a reason for hiding this comment

The 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;
Copy link
Collaborator

Choose a reason for hiding this comment

The 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) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

File.pathSeparator

if (lib.endsWith("rt.jar")) {
File f = new File(lib);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will that work for java 9?
Should we care now or later?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, I would care if rt is not part of java 9

Copy link
Owner Author

Choose a reason for hiding this comment

The 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.

😱

Copy link
Owner Author

Choose a reason for hiding this comment

The 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.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#87

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) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I meant was:

             String cleanRelPath = stripTrailingFileSeparators();
         if (!index.containsKey(cleanRelPath)) {
             index.put(cleanRelPath, delegate);
         }

And using the same strip-Method for lookup method of index, too.
Seems fishy for me to add multiples here but don't strip on lookup.

Copy link
Owner Author

Choose a reason for hiding this comment

The 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)) {
Copy link
Collaborator

Choose a reason for hiding this comment

The 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.
File separator is always char and has constants for string and char.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybeIndexResource(relPath.substring(0, relPath.length() - File.separator.length()), delegate);
}
}
}
}
Loading