-
Notifications
You must be signed in to change notification settings - Fork 78
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
serialized parsers #1815
serialized parsers #1815
Changes from all commits
93faa5b
fb319d7
5759ad8
7ab7be7
6227783
1011cde
d7abd51
addd58a
f64a18e
6dadd63
6024915
800c6c3
b643d7a
6ba6a97
db7f82f
01cbace
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 |
---|---|---|
|
@@ -16,6 +16,7 @@ | |
|
||
import java.io.ByteArrayInputStream; | ||
import java.io.ByteArrayOutputStream; | ||
import java.io.FileNotFoundException; | ||
import java.io.IOException; | ||
import java.io.InputStream; | ||
import java.io.OutputStream; | ||
|
@@ -28,6 +29,11 @@ | |
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Map.Entry; | ||
import java.util.jar.Attributes; | ||
import java.util.jar.JarEntry; | ||
import java.util.jar.JarInputStream; | ||
import java.util.jar.JarOutputStream; | ||
import java.util.jar.Manifest; | ||
import java.util.Set; | ||
|
||
import javax.tools.DiagnosticCollector; | ||
|
@@ -41,7 +47,9 @@ | |
import javax.tools.StandardLocation; | ||
import javax.tools.ToolProvider; | ||
|
||
import org.rascalmpl.library.Prelude; | ||
import org.rascalmpl.uri.URIUtil; | ||
import org.rascalmpl.uri.jar.JarInputStreamFileTree; | ||
|
||
/** | ||
* Compile a String or other {@link CharSequence}, returning a Java | ||
|
@@ -162,10 +170,13 @@ public synchronized Class<T> compile(final String qualifiedClassName, | |
final DiagnosticCollector<JavaFileObject> diagnosticsList, | ||
final Class<?>... types) throws JavaCompilerException, | ||
ClassCastException { | ||
if (diagnosticsList != null) | ||
if (diagnosticsList != null) { | ||
diagnostics = diagnosticsList; | ||
else | ||
} | ||
else { | ||
diagnostics = new DiagnosticCollector<JavaFileObject>(); | ||
} | ||
|
||
Map<String, CharSequence> classes = new HashMap<String, CharSequence>(1); | ||
classes.put(qualifiedClassName, javaSource); | ||
Map<String, Class<T>> compiled = compile(classes, diagnostics); | ||
|
@@ -245,6 +256,28 @@ public synchronized Map<String, Class<T>> compile( | |
} | ||
} | ||
|
||
public void compile(OutputStream classBytes, String qualifiedClassName, CharSequence classSource, final DiagnosticCollector<JavaFileObject> diagnostics) throws JavaCompilerException { | ||
Map<String, CharSequence> fileMap = new HashMap<>(); | ||
|
||
try { | ||
fileMap.put(qualifiedClassName, classSource); | ||
|
||
// ignoring return class here | ||
compile(fileMap, diagnostics); | ||
|
||
// side-effect alert: | ||
// now the local classloader contains the .class file | ||
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. is this a new side-effect? or was this always a side-effect? 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. it was always there, and necessary for the generated sub-classes bytecode to become available when instantiating the parser class object. However, normally we'd not ignore the return class and we would not be reading ourselves directly from the virtual file system later. Definitely not |
||
classLoader.outputClassesToJar(qualifiedClassName, classBytes); | ||
} | ||
catch (IOException e) { | ||
throw new JavaCompilerException(fileMap.keySet(), e, diagnostics); | ||
} | ||
} | ||
|
||
public Class<?> load(InputStream file) throws IOException, ClassNotFoundException { | ||
return classLoader.inputClassesFromJar(file); | ||
} | ||
|
||
/** | ||
* Load a class that was generated by this instance or accessible from its | ||
* parent class loader. Use this method if you need access to additional | ||
|
@@ -310,6 +343,8 @@ public ClassLoader getClassLoader() { | |
public JavaFileManager getFileManager() { | ||
return javaFileManager; | ||
} | ||
|
||
|
||
} | ||
|
||
/** | ||
|
@@ -561,6 +596,51 @@ final class ClassLoaderImpl extends ClassLoader { | |
super(parentClassLoader); | ||
} | ||
|
||
public void outputClassesToJar(String qualifiedClassName, OutputStream output) throws IOException { | ||
Manifest manifest = new Manifest(); | ||
manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0"); | ||
manifest.getMainAttributes().put(Attributes.Name.MAIN_CLASS, qualifiedClassName); | ||
|
||
try (JarOutputStream target = new JarOutputStream(output, manifest)) { | ||
for (Entry<String, JavaFileObject> entry : classes.entrySet()) { | ||
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. so this saves all previously compiled classes to the file? 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. yes, but the compiler is organized to be instantiated per run. So you get all the class files generated from compiling one Java source file loaded into one virtual file system that is wrapped by one classloader. 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. In the case of parser generation, this entails one main file and as many sub-classes as there are non-terminals (including lists etc.) in the grammar. 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. If you need to compile several collaborating source files, you can use 1 instance and still save the class files into one comprehensive jar file that will work if you use the Note that I have chosen to not use |
||
String className = entry.getKey(); | ||
JavaFileObjectImpl file = (JavaFileObjectImpl) entry.getValue(); | ||
JarEntry jarEntry = new JarEntry(className); | ||
jarEntry.setTime(file.getLastModified()); | ||
target.putNextEntry(jarEntry); | ||
target.write(file.getByteCode()); | ||
target.closeEntry(); | ||
} | ||
} | ||
} | ||
|
||
public Class<?> inputClassesFromJar(InputStream in) throws IOException, ClassNotFoundException { | ||
try (JarInputStream jarIn = new JarInputStream(in)) { | ||
Manifest mf = jarIn.getManifest(); | ||
String mainClass = (String) mf.getMainAttributes().get(Attributes.Name.MAIN_CLASS); | ||
JarEntry jarEntry; | ||
|
||
if (mainClass == null) { | ||
throw new IOException("missing Main-Class in jar manifest"); | ||
} | ||
|
||
while ((jarEntry = jarIn.getNextJarEntry()) != null) { | ||
if (!jarEntry.isDirectory()) { | ||
var className = jarEntry.getName(); | ||
var file = new JavaFileObjectImpl(className, JavaFileObject.Kind.CLASS); | ||
|
||
try (var fo = file.openOutputStream()) { | ||
fo.write(Prelude.consumeInputStream(jarIn)); | ||
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. we could use a regular copy loop, to avoid loading it all into memory first? 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. yes, but so far this code has proven to be blazingly fast so I thought this is nice and short as it is. We average around 4 ms for storing or reading a medium sized grammar (so that includes reading and writing all byte arrays for the JVM bytecode of all sub-classes and the main parser class. |
||
} | ||
|
||
add(className, file); | ||
} | ||
} | ||
|
||
return loadClass(mainClass); | ||
} | ||
} | ||
|
||
/** | ||
* @return An collection of JavaFileObject instances for the classes in the | ||
* class loader. | ||
|
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.
this is necessary due to double presence of rascal.jar during the tutor run. For some reason the old builtins are linked against the new code.
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.
have to fix this in the tutor maven-plugin somehow.
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.
and errors as warnings fixes this? interesting
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.
well yes, the tutor fails then on the ParseTree module, but the build isn't stopped by this because those errors are reported as warnings only. When I do a full bootstrap, the error goes away again, but I'd rather not have te tutor's own run-time interfere with the run-time of the examples it is running.. very difficult in the case of rascal.jar. I believe it will require JVM module layers to fix this.