diff --git a/src/main/java/com/github/pfmiles/dropincc/Lang.java b/src/main/java/com/github/pfmiles/dropincc/Lang.java index 29c25d3..08a43b4 100644 --- a/src/main/java/com/github/pfmiles/dropincc/Lang.java +++ b/src/main/java/com/github/pfmiles/dropincc/Lang.java @@ -54,6 +54,9 @@ public class Lang implements Serializable { // warn messages generated while analyzing private String warnings; + // compilation encoding + private String encoding = "UTF-8"; + /** * Create language object with a name * @@ -94,7 +97,8 @@ public TokenDef newToken(String regExpr) { */ public ConstructingGrule defineGrule(Object... eles) { if (eles == null || eles.length == 0) - throw new DropinccException("Could not add empty grammar rule, if you want to add a rule alternative that matches nothing, use CC.NOTHING."); + throw new DropinccException( + "Could not add empty grammar rule, if you want to add a rule alternative that matches nothing, use CC.NOTHING."); Grule g = new Grule(this.grules.size()); Element[] elements = Util.filterProductionEles(eles); g.getAlts().add(new Alternative(elements)); @@ -108,7 +112,7 @@ public ConstructingGrule defineGrule(Object... eles) { */ public Exe compile() { checkIfAnyEmptyGrule(this.grules); - AnalyzedLang cl = new AnalyzedLang(this.name, this.tokens, this.grules, this.whiteSpaceSensitive); + AnalyzedLang cl = new AnalyzedLang(this.name, this.tokens, this.grules, this.whiteSpaceSensitive, this.encoding); cl.compile(); this.debugMsgs = cl.getDebugMsgs(); this.warnings = cl.getWarnings(); @@ -196,4 +200,14 @@ public String getWarnings() { return warnings; } + /** + * Set the encoding used during the compilation progress. Defaults to + * 'UTF-8' if not set. + * + * @param encoding + */ + public void setEncoding(String encoding) { + this.encoding = encoding; + } + } diff --git a/src/main/java/com/github/pfmiles/dropincc/impl/AnalyzedLang.java b/src/main/java/com/github/pfmiles/dropincc/impl/AnalyzedLang.java index bdd464b..68db577 100644 --- a/src/main/java/com/github/pfmiles/dropincc/impl/AnalyzedLang.java +++ b/src/main/java/com/github/pfmiles/dropincc/impl/AnalyzedLang.java @@ -99,8 +99,12 @@ public class AnalyzedLang { private String debugMsgs; private String warnings; - public AnalyzedLang(String name, List tokens, List grules, boolean whitespaceSensitive) { + // compilation encoding + private String encoding; + + public AnalyzedLang(String name, List tokens, List grules, boolean whitespaceSensitive, String encoding) { this.langName = name; + this.encoding = encoding; // build token -> tokenType mapping this.tokens = tokens; // Gathering instant tokenDefs... @@ -128,7 +132,8 @@ public void compile() { this.tokenPatterns = compiledTokenUnit.getRight(); // 2.resolving the parser ast - TypeMappingParam typeMappingParam = new TypeMappingParam(this.tokenTypeMapping, this.gruleTypeMapping, specialTypeMapping, this.kleeneTypeMapping); + TypeMappingParam typeMappingParam = new TypeMappingParam(this.tokenTypeMapping, this.gruleTypeMapping, specialTypeMapping, + this.kleeneTypeMapping); // at this point, 'gruleTypeMapping' contains all grule -> type // mappings, including generated grules this.ruleTypeToAlts = ParserCompiler.buildRuleTypeToAlts(typeMappingParam); @@ -161,7 +166,8 @@ public void compile() { this.parserCode = parserCodeGenResult.getCode(); // 7.compile and maintain the code in a separate classloader - CompilationResult result = HotCompileUtil.compile("com.github.pfmiles.dropincc.impl.runtime.gen." + this.langName, this.parserCode); + CompilationResult result = HotCompileUtil.compile("com.github.pfmiles.dropincc.impl.runtime.gen." + this.langName, this.parserCode, + this.encoding); if (!result.isSucceed()) { throw new DropinccException("Parser code compilation failed. Reason: " + result.getErrMsg()); } diff --git a/src/main/java/com/github/pfmiles/dropincc/impl/hotcompile/HotCompileClassLoader.java b/src/main/java/com/github/pfmiles/dropincc/impl/hotcompile/HotCompileClassLoader.java index b89bb95..59cbfcb 100644 --- a/src/main/java/com/github/pfmiles/dropincc/impl/hotcompile/HotCompileClassLoader.java +++ b/src/main/java/com/github/pfmiles/dropincc/impl/hotcompile/HotCompileClassLoader.java @@ -10,12 +10,7 @@ ******************************************************************************/ package com.github.pfmiles.dropincc.impl.hotcompile; -import java.io.File; -import java.io.FileInputStream; - -import com.github.pfmiles.dropincc.DropinccException; -import com.github.pfmiles.dropincc.impl.util.ByteAppender; -import com.github.pfmiles.dropincc.impl.util.Util; +import java.util.Map; /** * Load hot compiled code from default directory. @@ -25,37 +20,16 @@ */ public class HotCompileClassLoader extends ClassLoader { - public HotCompileClassLoader(ClassLoader parent) { + private Map inMemCls; + + public HotCompileClassLoader(ClassLoader parent, Map clses) { super(parent); + this.inMemCls = clses; } protected Class findClass(String name) throws ClassNotFoundException { - byte[] b = loadClassData(name); + byte[] b = this.inMemCls.get(name).getClsBytes(); return defineClass(name, b, 0, b.length); } - // read class data from default hot compilation directory - private byte[] loadClassData(String name) { - File src = new File(HotCompileConstants.TARGETDIR + Util.PATH_SEP + name.replace(".", Util.PATH_SEP) + ".class"); - FileInputStream fis = null; - try { - fis = new FileInputStream(src); - ByteAppender ba = new ByteAppender(); - byte[] buf = new byte[1024]; - int count = fis.read(buf); - while (count != -1) { - ba.append(buf, 0, count); - count = fis.read(buf); - } - return ba.toByteArray(); - } catch (Exception e) { - throw new DropinccException(e); - } finally { - try { - fis.close(); - } catch (Exception e) { - throw new DropinccException(e); - } - } - } } diff --git a/src/main/java/com/github/pfmiles/dropincc/impl/hotcompile/HotCompileConstants.java b/src/main/java/com/github/pfmiles/dropincc/impl/hotcompile/HotCompileConstants.java index 92ff701..7e6676f 100644 --- a/src/main/java/com/github/pfmiles/dropincc/impl/hotcompile/HotCompileConstants.java +++ b/src/main/java/com/github/pfmiles/dropincc/impl/hotcompile/HotCompileConstants.java @@ -23,8 +23,4 @@ public interface HotCompileConstants { */ String CLASSPATH = Util.getClassPath(); - /** - * hot compilation target directory - */ - String TARGETDIR = Util.getTempDirWithFileSeparatorSuffix() + "dcHotCompile"; } diff --git a/src/main/java/com/github/pfmiles/dropincc/impl/hotcompile/HotCompileUtil.java b/src/main/java/com/github/pfmiles/dropincc/impl/hotcompile/HotCompileUtil.java index c8cbb0f..7812140 100644 --- a/src/main/java/com/github/pfmiles/dropincc/impl/hotcompile/HotCompileUtil.java +++ b/src/main/java/com/github/pfmiles/dropincc/impl/hotcompile/HotCompileUtil.java @@ -10,22 +10,23 @@ ******************************************************************************/ package com.github.pfmiles.dropincc.impl.hotcompile; -import java.io.File; import java.io.StringWriter; import java.nio.charset.Charset; import java.util.Arrays; +import java.util.HashMap; import java.util.List; -import java.util.Locale; +import java.util.Map; import javax.tools.Diagnostic; import javax.tools.DiagnosticCollector; import javax.tools.JavaCompiler; import javax.tools.JavaCompiler.CompilationTask; +import javax.tools.JavaFileManager; import javax.tools.JavaFileObject; -import javax.tools.StandardJavaFileManager; import javax.tools.ToolProvider; import com.github.pfmiles.dropincc.DropinccException; +import com.github.pfmiles.dropincc.impl.util.Util; /** * Compiles code at runtime, using JDK1.6 compiler API. @@ -43,17 +44,16 @@ public class HotCompileUtil { * @return The resulting java class object and its corresponding class * loader. */ - public static CompilationResult compile(String qualifiedName, String sourceCode) { + public static CompilationResult compile(String qualifiedName, String sourceCode, String encoding) { JavaStringSource source = new JavaStringSource(qualifiedName, sourceCode); List ss = Arrays.asList(source); - File dir = new File(HotCompileConstants.TARGETDIR); - if (!dir.exists()) - dir.mkdirs(); - List options = Arrays.asList("-d", HotCompileConstants.TARGETDIR, "-classpath", HotCompileConstants.CLASSPATH); + List options = Arrays.asList("-classpath", HotCompileConstants.CLASSPATH); JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); - StandardJavaFileManager fileManager = null; + + JavaFileManager fileManager = null; + Map clses = new HashMap(); try { - fileManager = compiler.getStandardFileManager(null, Locale.getDefault(), Charset.forName("UTF-8")); + fileManager = new MemClsFileManager(compiler.getStandardFileManager(null, null, Charset.forName(encoding)), clses); DiagnosticCollector diagnostics = new DiagnosticCollector(); StringWriter out = new StringWriter(); CompilationTask task = compiler.getTask(out, fileManager, diagnostics, options, null, ss); @@ -72,7 +72,7 @@ public static CompilationResult compile(String qualifiedName, String sourceCode) } } // every parser class should be loaded by a new specific class loader - HotCompileClassLoader loader = new HotCompileClassLoader(HotCompileUtil.class.getClassLoader()); + HotCompileClassLoader loader = new HotCompileClassLoader(Util.getParentClsLoader(), clses); Class cls = null; try { cls = loader.loadClass(qualifiedName); diff --git a/src/main/java/com/github/pfmiles/dropincc/impl/hotcompile/JavaMemCls.java b/src/main/java/com/github/pfmiles/dropincc/impl/hotcompile/JavaMemCls.java new file mode 100644 index 0000000..40f34ba --- /dev/null +++ b/src/main/java/com/github/pfmiles/dropincc/impl/hotcompile/JavaMemCls.java @@ -0,0 +1,32 @@ +package com.github.pfmiles.dropincc.impl.hotcompile; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.net.URI; + +import javax.tools.SimpleJavaFileObject; + +/** + * Represents compiled java class files in the memory. + * + * @author pf-miles + * + */ +public class JavaMemCls extends SimpleJavaFileObject { + + private ByteArrayOutputStream bos; + + protected JavaMemCls(String name) { + super(URI.create("string:///" + name.replaceAll("\\.", "/") + Kind.CLASS.extension), Kind.CLASS); + this.bos = new ByteArrayOutputStream(); + } + + public byte[] getClsBytes() { + return this.bos.toByteArray(); + } + + public OutputStream openOutputStream() throws IOException { + return bos; + } +} diff --git a/src/main/java/com/github/pfmiles/dropincc/impl/hotcompile/MemClsFileManager.java b/src/main/java/com/github/pfmiles/dropincc/impl/hotcompile/MemClsFileManager.java new file mode 100644 index 0000000..4224450 --- /dev/null +++ b/src/main/java/com/github/pfmiles/dropincc/impl/hotcompile/MemClsFileManager.java @@ -0,0 +1,44 @@ +package com.github.pfmiles.dropincc.impl.hotcompile; + +import java.io.IOException; +import java.util.Map; + +import javax.tools.FileObject; +import javax.tools.ForwardingJavaFileManager; +import javax.tools.JavaFileObject; +import javax.tools.JavaFileObject.Kind; +import javax.tools.StandardJavaFileManager; + +/** + * A forwarding file manager which is used to decouple the file system + * dependency. That is, the compilation process write compiled class files into + * memory instead of file system. + * + * @author pf-miles + * + */ +public class MemClsFileManager extends ForwardingJavaFileManager { + + private Map destFiles; + + protected MemClsFileManager(StandardJavaFileManager fileManager, Map destFiles) { + super(fileManager); + this.destFiles = destFiles; + } + + public JavaFileObject getJavaFileForOutput(Location location, String className, Kind kind, FileObject sibling) throws IOException { + if (destFiles.containsKey(className)) { + return destFiles.get(className); + } else { + JavaMemCls file = new JavaMemCls(className); + this.destFiles.put(className, file); + return file; + } + } + + public void close() throws IOException { + super.close(); + this.destFiles = null; + } + +} diff --git a/src/main/java/com/github/pfmiles/dropincc/impl/util/Util.java b/src/main/java/com/github/pfmiles/dropincc/impl/util/Util.java index ffd7b34..5737724 100644 --- a/src/main/java/com/github/pfmiles/dropincc/impl/util/Util.java +++ b/src/main/java/com/github/pfmiles/dropincc/impl/util/Util.java @@ -221,19 +221,6 @@ public static String join(String joint, List elements) { return sb.toString(); } - /** - * Get the platform dependent temporary directory path, with 'file - * separator' suffix('/' on linux platform, '\' on windows). - * - * @return - */ - public static String getTempDirWithFileSeparatorSuffix() { - String p = System.getProperty("java.io.tmpdir"); - if (p.endsWith(File.separator)) - return p; - return p + PATH_SEP; - } - /** * build the classpath using all properties with suffix 'class.path'; to * make some software setting its own class path variables happy(like @@ -271,4 +258,21 @@ public static String getClassPath() { } return sb.toString(); } + + /** + * Get the proper parent class loader for hot compilation class loaders. + * + * @return + */ + public static ClassLoader getParentClsLoader() { + ClassLoader ctxLoader = Thread.currentThread().getContextClassLoader(); + if (ctxLoader != null) { + try { + ctxLoader.loadClass(Util.class.getName()); + return ctxLoader; + } catch (ClassNotFoundException e) { + } + } + return Util.class.getClassLoader(); + } } diff --git a/src/test/java/com/github/pfmiles/dropincc/impl/AnalyzedLangTest.java b/src/test/java/com/github/pfmiles/dropincc/impl/AnalyzedLangTest.java index 0dab612..c2e5a14 100644 --- a/src/test/java/com/github/pfmiles/dropincc/impl/AnalyzedLangTest.java +++ b/src/test/java/com/github/pfmiles/dropincc/impl/AnalyzedLangTest.java @@ -60,8 +60,8 @@ public void testSubRuleRewriteWithKleeneNodes() { addition.define(addend, CC.ks(ADD.or(SUB), addend)); addend.define(factor, CC.ks(MUL.or(DIV), factor)); factor.define(DIGIT).alt(LEFTPAREN, addition, RIGHTPAREN); - AnalyzedLang cl = new AnalyzedLang("test", (List) TestHelper.priField(calculator, "tokens"), (List) TestHelper.priField(calculator, "grules"), - false); + AnalyzedLang cl = new AnalyzedLang("test", (List) TestHelper.priField(calculator, "tokens"), (List) TestHelper.priField( + calculator, "grules"), false, "UTF-8"); KleeneStarNode k1 = (KleeneStarNode) addition.getAlts().get(0).getElements().get(1); Object shouldBeRewritten = k1.getElements().get(0); assertTrue(shouldBeRewritten instanceof Grule); diff --git a/src/test/java/com/github/pfmiles/dropincc/impl/hotcompile/HotCompileUtilTest.java b/src/test/java/com/github/pfmiles/dropincc/impl/hotcompile/HotCompileUtilTest.java index 6862410..93c77a5 100644 --- a/src/test/java/com/github/pfmiles/dropincc/impl/hotcompile/HotCompileUtilTest.java +++ b/src/test/java/com/github/pfmiles/dropincc/impl/hotcompile/HotCompileUtilTest.java @@ -20,7 +20,7 @@ public class HotCompileUtilTest extends TestCase { public void testCompile() throws Exception { String code = "package test;\n" + "public class Test {\n" + "public static void main(String... args) throws Throwable {\n" + "System.out.println(\"Hello World.\");\n" + "}\n" + "};\n"; - CompilationResult rst = HotCompileUtil.compile("test.Test", code); + CompilationResult rst = HotCompileUtil.compile("test.Test", code, "UTF-8"); if (!rst.isSucceed()) { assertTrue(false); } else { diff --git a/src/test/java/com/github/pfmiles/dropincc/testhelper/TestHelper.java b/src/test/java/com/github/pfmiles/dropincc/testhelper/TestHelper.java index 2b8d4a7..1055fc1 100644 --- a/src/test/java/com/github/pfmiles/dropincc/testhelper/TestHelper.java +++ b/src/test/java/com/github/pfmiles/dropincc/testhelper/TestHelper.java @@ -106,11 +106,11 @@ public static T priField(Object obj, String fieldName) { @SuppressWarnings("unchecked") public static AnalyzedLangForTest resolveAnalyzedLangForTest(Lang lang) { AnalyzedLangForTest ret = new AnalyzedLangForTest(); - AnalyzedLang al = new AnalyzedLang("test", (List) priField(lang, "tokens"), (List) priField(lang, "grules"), (Boolean) priField(lang, - "whiteSpaceSensitive")); + AnalyzedLang al = new AnalyzedLang("test", (List) priField(lang, "tokens"), (List) priField(lang, "grules"), + (Boolean) priField(lang, "whiteSpaceSensitive"), "UTF-8"); TypeMappingParam typeMappingParam = new TypeMappingParam((Map) TestHelper.priField(al, "tokenTypeMapping"), - (Map) TestHelper.priField(al, "gruleTypeMapping"), (Map) TestHelper.priField(al, "specialTypeMapping"), - (Map) TestHelper.priField(al, "kleeneTypeMapping")); + (Map) TestHelper.priField(al, "gruleTypeMapping"), (Map) TestHelper.priField(al, + "specialTypeMapping"), (Map) TestHelper.priField(al, "kleeneTypeMapping")); ret.kleeneTypeToNode = KleeneCompiler.buildKleeneTypeToNode(typeMappingParam); ret.ruleTypeToAlts = ParserCompiler.buildRuleTypeToAlts(typeMappingParam); return ret;