From eb2b825d95971c12e05620c30310fd1cabc3b2b5 Mon Sep 17 00:00:00 2001 From: Brian Dahlem Date: Wed, 3 Sep 2025 13:56:17 -0700 Subject: [PATCH 01/13] Update ECJ compiler and support in-memory files --- pom.xml | 20 ++---- .../compiler/InMemoryJavaFileManager.java | 64 +++++++++++++++---- .../compiler/InMemoryJavaFileObject.java | 46 +++++++------ .../run/myCode/compiler/JavaCodeCompiler.java | 7 +- 4 files changed, 87 insertions(+), 50 deletions(-) diff --git a/pom.xml b/pom.xml index aef3cf6..e3c8afe 100644 --- a/pom.xml +++ b/pom.xml @@ -8,17 +8,7 @@ 1.8 UTF-8 - - - cagatay-gurturk - http://maven.cagataygurturk.com/releases - - - + windows-profile @@ -95,10 +85,10 @@ org.eclipse.jdt - ecj - (,3.13.0] - - + ecj + 3.42.0 + + junit diff --git a/src/main/java/run/myCode/compiler/InMemoryJavaFileManager.java b/src/main/java/run/myCode/compiler/InMemoryJavaFileManager.java index fc32b10..f7aefd9 100644 --- a/src/main/java/run/myCode/compiler/InMemoryJavaFileManager.java +++ b/src/main/java/run/myCode/compiler/InMemoryJavaFileManager.java @@ -1,17 +1,21 @@ package run.myCode.compiler; -import java.io.IOException; - -import javax.tools.FileObject; -import javax.tools.ForwardingJavaFileManager; -import javax.tools.JavaFileObject; -import javax.tools.StandardJavaFileManager; -import javax.tools.JavaFileManager.Location; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import javax.tools.FileObject; +import javax.tools.ForwardingJavaFileManager; +import javax.tools.JavaFileObject; +import javax.tools.StandardJavaFileManager; +import javax.tools.JavaFileManager.Location; @SuppressWarnings({"unused", "rawtypes"}) public class InMemoryJavaFileManager extends ForwardingJavaFileManager { - private final FromMemoryClassLoader xcl; + private final FromMemoryClassLoader xcl; + private final Map sources = new HashMap<>(); + private final Map sourcePaths = new HashMap<>(); @SuppressWarnings("unchecked") public InMemoryJavaFileManager(StandardJavaFileManager sjfm, FromMemoryClassLoader xcl) { @@ -19,12 +23,44 @@ public InMemoryJavaFileManager(StandardJavaFileManager sjfm, FromMemoryClassLoad this.xcl = xcl; } - @Override - public JavaFileObject getJavaFileForOutput(Location location, String name, JavaFileObject.Kind kind, FileObject sibling) throws IOException { - MemoryByteCode mbc = new MemoryByteCode(name); - xcl.addClass(name, mbc); - return mbc; - } + public void addSource(JavaFileObject file) { + // Store by binary name derived from file name + String path = file.getName(); + if (path.startsWith("/")) { + path = path.substring(1); + } + sourcePaths.put(path, file); + if (path.endsWith(".java")) { + String className = path.substring(0, path.length() - 5); + sources.put(className, file); + } + } + + @Override + public JavaFileObject getJavaFileForInput(Location location, String className, JavaFileObject.Kind kind) throws IOException { + JavaFileObject file = sources.get(className); + if (file != null) { + return file; + } + return super.getJavaFileForInput(location, className, kind); + } + + @Override + public FileObject getFileForInput(Location location, String packageName, String relativeName) throws IOException { + String path = packageName == null || packageName.isEmpty() ? relativeName : packageName + "/" + relativeName; + JavaFileObject file = sourcePaths.get(path); + if (file != null) { + return file; + } + return super.getFileForInput(location, packageName, relativeName); + } + + @Override + public JavaFileObject getJavaFileForOutput(Location location, String name, JavaFileObject.Kind kind, FileObject sibling) throws IOException { + MemoryByteCode mbc = new MemoryByteCode(name); + xcl.addClass(name, mbc); + return mbc; + } @Override public ClassLoader getClassLoader(Location location) { diff --git a/src/main/java/run/myCode/compiler/InMemoryJavaFileObject.java b/src/main/java/run/myCode/compiler/InMemoryJavaFileObject.java index 674fc50..2bd9038 100644 --- a/src/main/java/run/myCode/compiler/InMemoryJavaFileObject.java +++ b/src/main/java/run/myCode/compiler/InMemoryJavaFileObject.java @@ -1,11 +1,14 @@ package run.myCode.compiler; -import java.io.IOException; -import java.net.URI; - -import javax.tools.SimpleJavaFileObject; - -import javax.tools.JavaFileObject.Kind; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.nio.charset.StandardCharsets; + +import javax.tools.SimpleJavaFileObject; + +import javax.tools.JavaFileObject.Kind; /** * java File Object represents an in-memory java source file so there is no need @@ -21,19 +24,24 @@ public class InMemoryJavaFileObject extends SimpleJavaFileObject { * @param fileName the name of the file, with extension * @param contents the contents of the file as a single string object */ - public InMemoryJavaFileObject(String fileName, String contents) { - // Create a file object with a classname instead of a filename by - // removing the file's extension and convert the . separators into slashes - super(URI.create("file:///" + fileName), Kind.SOURCE); - - // Save the file's contents - this.contents = contents; - } - - @Override - public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { - return contents; - } + public InMemoryJavaFileObject(String fileName, String contents) { + // Create a file object with a classname instead of a filename by + // removing the file's extension and convert the . separators into slashes + super(URI.create("string:///" + fileName), Kind.SOURCE); + + // Save the file's contents + this.contents = contents; + } + + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { + return contents; + } + + @Override + public InputStream openInputStream() throws IOException { + return new ByteArrayInputStream(contents.getBytes(StandardCharsets.UTF_8)); + } @Override public String toString() { diff --git a/src/main/java/run/myCode/compiler/JavaCodeCompiler.java b/src/main/java/run/myCode/compiler/JavaCodeCompiler.java index 861d0d1..7cc4717 100644 --- a/src/main/java/run/myCode/compiler/JavaCodeCompiler.java +++ b/src/main/java/run/myCode/compiler/JavaCodeCompiler.java @@ -61,7 +61,10 @@ public static FromMemoryClassLoader compile(Iterable f final CompileDiagnosticListener diag = new CompileDiagnosticListener(); final StandardJavaFileManager stdfileManager = compiler.getStandardFileManager(diag, Locale.ENGLISH, null); - InMemoryJavaFileManager fileManager = new InMemoryJavaFileManager(stdfileManager, classLoader); + InMemoryJavaFileManager fileManager = new InMemoryJavaFileManager(stdfileManager, classLoader); + for (JavaFileObject file : files) { + fileManager.addSource(file); + } // specify options for compiler if (options == null) { @@ -97,7 +100,7 @@ public static FromMemoryClassLoader compile(Iterable f // Set the classpath and java version for the compiler options.addAll(Arrays.asList("-classpath", classpath)); - options.addAll(Arrays.asList("-1.8")); + options.addAll(Arrays.asList("--release", "8")); Writer out = new PrintWriter(System.out); From c8ba85e371a8042213b63507898efa9e7f0dfcbb Mon Sep 17 00:00:00 2001 From: Brian Dahlem Date: Wed, 3 Sep 2025 14:06:32 -0700 Subject: [PATCH 02/13] Handle leading slashes in in-memory file lookups --- .../run/myCode/compiler/InMemoryJavaFileManager.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/java/run/myCode/compiler/InMemoryJavaFileManager.java b/src/main/java/run/myCode/compiler/InMemoryJavaFileManager.java index f7aefd9..02ce793 100644 --- a/src/main/java/run/myCode/compiler/InMemoryJavaFileManager.java +++ b/src/main/java/run/myCode/compiler/InMemoryJavaFileManager.java @@ -38,7 +38,11 @@ public void addSource(JavaFileObject file) { @Override public JavaFileObject getJavaFileForInput(Location location, String className, JavaFileObject.Kind kind) throws IOException { - JavaFileObject file = sources.get(className); + String name = className; + if (name.startsWith("/")) { + name = name.substring(1); + } + JavaFileObject file = sources.get(name); if (file != null) { return file; } @@ -48,6 +52,9 @@ public JavaFileObject getJavaFileForInput(Location location, String className, J @Override public FileObject getFileForInput(Location location, String packageName, String relativeName) throws IOException { String path = packageName == null || packageName.isEmpty() ? relativeName : packageName + "/" + relativeName; + if (path.startsWith("/")) { + path = path.substring(1); + } JavaFileObject file = sourcePaths.get(path); if (file != null) { return file; From f1cbfd43c7fa3cf5b21d16633bcf6476c18a8b44 Mon Sep 17 00:00:00 2001 From: Brian Dahlem Date: Wed, 3 Sep 2025 14:21:20 -0700 Subject: [PATCH 03/13] Handle custom memory URIs and binary name resolution --- .../compiler/InMemoryJavaFileManager.java | 24 +++++++++++++++++-- .../compiler/InMemoryJavaFileObject.java | 11 ++++++--- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/main/java/run/myCode/compiler/InMemoryJavaFileManager.java b/src/main/java/run/myCode/compiler/InMemoryJavaFileManager.java index 02ce793..93057c8 100644 --- a/src/main/java/run/myCode/compiler/InMemoryJavaFileManager.java +++ b/src/main/java/run/myCode/compiler/InMemoryJavaFileManager.java @@ -24,16 +24,36 @@ public InMemoryJavaFileManager(StandardJavaFileManager sjfm, FromMemoryClassLoad } public void addSource(JavaFileObject file) { - // Store by binary name derived from file name + // The JavaFileObject#getName returns the URI path. Normalise it so it + // can be looked up with or without a leading slash and use the path to + // derive the binary name for the class. String path = file.getName(); if (path.startsWith("/")) { path = path.substring(1); } sourcePaths.put(path, file); if (path.endsWith(".java")) { - String className = path.substring(0, path.length() - 5); + String className = path.substring(0, path.length() - 5) + .replace('/', '.'); sources.put(className, file); + + // Also record the expected package path (e.g. foo/Bar.java) so + // lookups using package-qualified names can be resolved. + String pkgPath = className.replace('.', '/') + ".java"; + sourcePaths.put(pkgPath, file); + } + } + + @Override + public String inferBinaryName(Location location, JavaFileObject file) { + String name = file.getName(); + if (name.startsWith("/")) { + name = name.substring(1); + } + if (name.endsWith(".java")) { + name = name.substring(0, name.length() - 5); } + return name.replace('/', '.'); } @Override diff --git a/src/main/java/run/myCode/compiler/InMemoryJavaFileObject.java b/src/main/java/run/myCode/compiler/InMemoryJavaFileObject.java index 2bd9038..e7b29ad 100644 --- a/src/main/java/run/myCode/compiler/InMemoryJavaFileObject.java +++ b/src/main/java/run/myCode/compiler/InMemoryJavaFileObject.java @@ -25,9 +25,14 @@ public class InMemoryJavaFileObject extends SimpleJavaFileObject { * @param contents the contents of the file as a single string object */ public InMemoryJavaFileObject(String fileName, String contents) { - // Create a file object with a classname instead of a filename by - // removing the file's extension and convert the . separators into slashes - super(URI.create("string:///" + fileName), Kind.SOURCE); + // Use a custom URI scheme so the Eclipse compiler does not resolve the + // file on disk. The URI's path is the provided filename which keeps + // the compiler from looking for a physical file like "/MyClass.java". + // + // Using a non-file scheme is important because ECJ will otherwise try + // to open the path returned by getName() from the filesystem which + // causes "File ... is missing" errors when compiling in memory. + super(URI.create("mem:///" + fileName), Kind.SOURCE); // Save the file's contents this.contents = contents; From 8499ecaa1436b1a6847e4fda9da9e8f47922889a Mon Sep 17 00:00:00 2001 From: Brian Dahlem Date: Wed, 3 Sep 2025 14:28:33 -0700 Subject: [PATCH 04/13] Add debugging for in-memory compilation --- .../compiler/FromMemoryClassLoader.java | 34 ++++++++++++------- .../compiler/InMemoryJavaFileManager.java | 19 +++++++++-- .../compiler/InMemoryJavaFileObject.java | 15 ++++++-- .../run/myCode/compiler/JavaCodeCompiler.java | 14 ++++++-- 4 files changed, 61 insertions(+), 21 deletions(-) diff --git a/src/main/java/run/myCode/compiler/FromMemoryClassLoader.java b/src/main/java/run/myCode/compiler/FromMemoryClassLoader.java index a8a8702..96ff919 100644 --- a/src/main/java/run/myCode/compiler/FromMemoryClassLoader.java +++ b/src/main/java/run/myCode/compiler/FromMemoryClassLoader.java @@ -5,10 +5,12 @@ import java.util.List; import java.util.Map; -public class FromMemoryClassLoader extends ClassLoader { - - private final Map m = new HashMap<>(); - private final ClassLoader parent; +public class FromMemoryClassLoader extends ClassLoader { + + private static final boolean DEBUG = Boolean.getBoolean("run.mycode.debug"); + + private final Map m = new HashMap<>(); + private final ClassLoader parent; public FromMemoryClassLoader(ClassLoader parent) { this.parent = parent; @@ -20,13 +22,16 @@ public Class findClass(String name) throws ClassNotFoundException { try { // System.out.println("Class search:" + name); MemoryByteCode mbc = m.get(name); - if (mbc == null) { - mbc = m.get(name.replace(".", "/")); - if (mbc == null) { - return super.findClass(name); - } - } - return defineClass(name, mbc.getBytes(), 0, mbc.getBytes().length); + if (mbc == null) { + mbc = m.get(name.replace(".", "/")); + if (mbc == null) { + return super.findClass(name); + } + } + if (DEBUG) { + System.out.println("Loading in-memory class " + name); + } + return defineClass(name, mbc.getBytes(), 0, mbc.getBytes().length); } catch (ClassNotFoundException e) { // System.err.println("Could not find: " + name); if (this.parent != null) { @@ -46,8 +51,11 @@ public Class findClass(String name) throws ClassNotFoundException { public void addClass(String name, MemoryByteCode mbc) { // System.err.println("Added class: " + name); // System.out.println("Added class:" + name); - m.put(name, mbc); - } + m.put(name, mbc); + if (DEBUG) { + System.out.println("Added in-memory class " + name); + } + } public List getClassNames() { return new ArrayList(m.keySet()); diff --git a/src/main/java/run/myCode/compiler/InMemoryJavaFileManager.java b/src/main/java/run/myCode/compiler/InMemoryJavaFileManager.java index 93057c8..7d228d1 100644 --- a/src/main/java/run/myCode/compiler/InMemoryJavaFileManager.java +++ b/src/main/java/run/myCode/compiler/InMemoryJavaFileManager.java @@ -11,8 +11,10 @@ import javax.tools.JavaFileManager.Location; @SuppressWarnings({"unused", "rawtypes"}) -public class InMemoryJavaFileManager extends ForwardingJavaFileManager { - +public class InMemoryJavaFileManager extends ForwardingJavaFileManager { + + private static final boolean DEBUG = Boolean.getBoolean("run.mycode.debug"); + private final FromMemoryClassLoader xcl; private final Map sources = new HashMap<>(); private final Map sourcePaths = new HashMap<>(); @@ -41,6 +43,10 @@ public void addSource(JavaFileObject file) { // lookups using package-qualified names can be resolved. String pkgPath = className.replace('.', '/') + ".java"; sourcePaths.put(pkgPath, file); + + if (DEBUG) { + System.out.println("Registered source " + file.getName()); + } } } @@ -63,6 +69,9 @@ public JavaFileObject getJavaFileForInput(Location location, String className, J name = name.substring(1); } JavaFileObject file = sources.get(name); + if (DEBUG) { + System.out.println("getJavaFileForInput(" + className + ") -> " + (file != null ? "memory" : "disk")); + } if (file != null) { return file; } @@ -76,6 +85,9 @@ public FileObject getFileForInput(Location location, String packageName, String path = path.substring(1); } JavaFileObject file = sourcePaths.get(path); + if (DEBUG) { + System.out.println("getFileForInput(" + path + ") -> " + (file != null ? "memory" : "disk")); + } if (file != null) { return file; } @@ -86,6 +98,9 @@ public FileObject getFileForInput(Location location, String packageName, String public JavaFileObject getJavaFileForOutput(Location location, String name, JavaFileObject.Kind kind, FileObject sibling) throws IOException { MemoryByteCode mbc = new MemoryByteCode(name); xcl.addClass(name, mbc); + if (DEBUG) { + System.out.println("Storing compiled class " + name); + } return mbc; } diff --git a/src/main/java/run/myCode/compiler/InMemoryJavaFileObject.java b/src/main/java/run/myCode/compiler/InMemoryJavaFileObject.java index e7b29ad..810bcaf 100644 --- a/src/main/java/run/myCode/compiler/InMemoryJavaFileObject.java +++ b/src/main/java/run/myCode/compiler/InMemoryJavaFileObject.java @@ -14,9 +14,11 @@ * java File Object represents an in-memory java source file so there is no need * to put the source file on hard disk */ -public class InMemoryJavaFileObject extends SimpleJavaFileObject { - - private String contents = null; +public class InMemoryJavaFileObject extends SimpleJavaFileObject { + + private static final boolean DEBUG = Boolean.getBoolean("run.mycode.debug"); + + private String contents = null; /** * Create a Java file object in memory with a name and text contents. @@ -36,6 +38,10 @@ public InMemoryJavaFileObject(String fileName, String contents) { // Save the file's contents this.contents = contents; + + if (DEBUG) { + System.out.println("Created in-memory source " + toUri()); + } } @Override @@ -45,6 +51,9 @@ public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOExcept @Override public InputStream openInputStream() throws IOException { + if (DEBUG) { + System.out.println("Opening in-memory source " + getName()); + } return new ByteArrayInputStream(contents.getBytes(StandardCharsets.UTF_8)); } diff --git a/src/main/java/run/myCode/compiler/JavaCodeCompiler.java b/src/main/java/run/myCode/compiler/JavaCodeCompiler.java index 7cc4717..f98295e 100644 --- a/src/main/java/run/myCode/compiler/JavaCodeCompiler.java +++ b/src/main/java/run/myCode/compiler/JavaCodeCompiler.java @@ -19,7 +19,9 @@ import org.eclipse.jdt.internal.compiler.tool.EclipseCompiler; -public class JavaCodeCompiler { +public class JavaCodeCompiler { + + private static final boolean DEBUG = Boolean.getBoolean("run.mycode.debug"); /** * Compile Java source files into memory @@ -64,6 +66,9 @@ public static FromMemoryClassLoader compile(Iterable f InMemoryJavaFileManager fileManager = new InMemoryJavaFileManager(stdfileManager, classLoader); for (JavaFileObject file : files) { fileManager.addSource(file); + if (DEBUG) { + System.out.println("Compiling source " + file.getName()); + } } // specify options for compiler @@ -105,8 +110,11 @@ public static FromMemoryClassLoader compile(Iterable f Writer out = new PrintWriter(System.out); // Compile the code - JavaCompiler.CompilationTask task = compiler.getTask(out, fileManager, diag, options, null, files); - boolean result = task.call(); + JavaCompiler.CompilationTask task = compiler.getTask(out, fileManager, diag, options, null, files); + if (DEBUG) { + System.out.println("Starting compilation with mem URIs"); + } + boolean result = task.call(); // Return the classloader containing the compiled classes return classLoader; From 2e8cda866adb1dab6c7ecb916bb6c2ac37b8f6a5 Mon Sep 17 00:00:00 2001 From: Brian Dahlem Date: Wed, 3 Sep 2025 14:38:41 -0700 Subject: [PATCH 05/13] Enable debug output in tests --- pom.xml | 45 ++++++++++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/pom.xml b/pom.xml index e3c8afe..b480bee 100644 --- a/pom.xml +++ b/pom.xml @@ -119,23 +119,34 @@ - - org.codehaus.mojo - exec-maven-plugin - 1.2.1 - - com.cagataygurturk.lambda.LocalRunner - - handleRequest - - - - - - org.apache.maven.plugins - maven-shade-plugin - 2.3 - + + org.codehaus.mojo + exec-maven-plugin + 1.2.1 + + com.cagataygurturk.lambda.LocalRunner + + handleRequest + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.2 + + + true + + + + + + org.apache.maven.plugins + maven-shade-plugin + 2.3 + false From 928cc5894c7a0a23bac9b56e54f631c95d6f855b Mon Sep 17 00:00:00 2001 From: Brian Dahlem Date: Wed, 3 Sep 2025 14:38:45 -0700 Subject: [PATCH 06/13] Update AWS SDK BOM --- pom.xml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/pom.xml b/pom.xml index b480bee..aab310b 100644 --- a/pom.xml +++ b/pom.xml @@ -38,22 +38,22 @@ - - com.amazonaws - aws-java-sdk-bom - 1.11.911 - pom - import - + + com.amazonaws + aws-java-sdk-bom + 1.12.789 + pom + import + - - com.amazonaws - aws-java-sdk-lambda - 1.11.893 - + + com.amazonaws + aws-java-sdk-lambda + 1.12.789 + com.amazonaws From 82451549916db301390224a42d5d61f56d0ef7d5 Mon Sep 17 00:00:00 2001 From: Brian Dahlem Date: Wed, 3 Sep 2025 14:47:42 -0700 Subject: [PATCH 07/13] Strip leading slash from in-memory source names --- src/main/java/run/myCode/FileManager.java | 46 ++++++++++++------- .../compiler/InMemoryJavaFileObject.java | 9 ++++ 2 files changed, 39 insertions(+), 16 deletions(-) diff --git a/src/main/java/run/myCode/FileManager.java b/src/main/java/run/myCode/FileManager.java index 1376a97..aa29611 100644 --- a/src/main/java/run/myCode/FileManager.java +++ b/src/main/java/run/myCode/FileManager.java @@ -5,9 +5,11 @@ import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; -import java.util.Base64; -import java.util.List; -import run.myCode.compiler.InMemoryJavaFileObject; +import java.util.Base64; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import run.myCode.compiler.InMemoryJavaFileObject; /** * @@ -24,19 +26,31 @@ public static List createSourceFileObjects(CompileReques List objects = new ArrayList<>(req.getSourceFiles().size()); - // Pull all of the source code files in the request - req.getSourceFiles().forEach((f) -> { - String contents = ""; - for (String s : f.getContents()) { - // Save the file's contents - contents += s + '\n'; - } - - // Add them to a list of files - objects.add(new InMemoryJavaFileObject(f.getName(), contents)); - }); - return objects; - } + // Pull all of the source code files in the request + req.getSourceFiles().forEach((f) -> { + String contents = ""; + for (String s : f.getContents()) { + // Save the file's contents + contents += s + '\n'; + } + + String fileName = f.getName(); + // If the source declares a package, prefix the file name with its + // package path so the compiler's file manager can resolve the + // class correctly. ECJ expects the source path to mirror the + // package declaration, otherwise it will try to locate the file on + // disk and fail with "File ... is missing". + Pattern pkgPattern = Pattern.compile("(?m)^\\s*package\\s+([\\w\\.]+)\\s*;"); + Matcher m = pkgPattern.matcher(contents); + if (m.find()) { + fileName = m.group(1).replace('.', '/') + "/" + fileName; + } + + // Add them to a list of files + objects.add(new InMemoryJavaFileObject(fileName, contents)); + }); + return objects; + } public static void saveData(DataRequest data) { if (data == null) { diff --git a/src/main/java/run/myCode/compiler/InMemoryJavaFileObject.java b/src/main/java/run/myCode/compiler/InMemoryJavaFileObject.java index 810bcaf..022f8c0 100644 --- a/src/main/java/run/myCode/compiler/InMemoryJavaFileObject.java +++ b/src/main/java/run/myCode/compiler/InMemoryJavaFileObject.java @@ -56,6 +56,15 @@ public InputStream openInputStream() throws IOException { } return new ByteArrayInputStream(contents.getBytes(StandardCharsets.UTF_8)); } + + @Override + public String getName() { + String path = toUri().getPath(); + if (path.startsWith("/")) { + return path.substring(1); + } + return path; + } @Override public String toString() { From 5d544fb3bcda760db2b074c64fcd4a0d530d4285 Mon Sep 17 00:00:00 2001 From: Brian Dahlem Date: Wed, 3 Sep 2025 14:58:52 -0700 Subject: [PATCH 08/13] Handle mem URIs in in-memory file manager --- .../myCode/compiler/InMemoryJavaFileManager.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/main/java/run/myCode/compiler/InMemoryJavaFileManager.java b/src/main/java/run/myCode/compiler/InMemoryJavaFileManager.java index 7d228d1..2527b4e 100644 --- a/src/main/java/run/myCode/compiler/InMemoryJavaFileManager.java +++ b/src/main/java/run/myCode/compiler/InMemoryJavaFileManager.java @@ -33,7 +33,11 @@ public void addSource(JavaFileObject file) { if (path.startsWith("/")) { path = path.substring(1); } + // Track both the plain path and the full URI so lookups using either + // form can be resolved (e.g. "Calculator.java" vs "mem:///Calculator.java"). sourcePaths.put(path, file); + sourcePaths.put(file.toUri().toString(), file); + if (path.endsWith(".java")) { String className = path.substring(0, path.length() - 5) .replace('/', '.'); @@ -68,6 +72,13 @@ public JavaFileObject getJavaFileForInput(Location location, String className, J if (name.startsWith("/")) { name = name.substring(1); } + if (name.startsWith("mem:///")) { + name = name.substring("mem:///".length()); + } + if (name.endsWith(".java")) { + name = name.substring(0, name.length() - 5); + } + name = name.replace('/', '.'); JavaFileObject file = sources.get(name); if (DEBUG) { System.out.println("getJavaFileForInput(" + className + ") -> " + (file != null ? "memory" : "disk")); @@ -84,6 +95,9 @@ public FileObject getFileForInput(Location location, String packageName, String if (path.startsWith("/")) { path = path.substring(1); } + if (path.startsWith("mem:///")) { + path = path.substring("mem:///".length()); + } JavaFileObject file = sourcePaths.get(path); if (DEBUG) { System.out.println("getFileForInput(" + path + ") -> " + (file != null ? "memory" : "disk")); From 5ca29a5065b9a4a5e3fac55df1dbd12991fab590 Mon Sep 17 00:00:00 2001 From: Brian Dahlem Date: Wed, 3 Sep 2025 15:36:20 -0700 Subject: [PATCH 09/13] Use JDK compiler for zombie tests --- .../compiler/InMemoryJavaFileObject.java | 15 +-- .../run/myCode/compiler/JavaCodeCompiler.java | 17 +-- .../zss/compiler/InMemoryJavaFileObject.java | 102 ++++++++++-------- .../java/zss/compiler/MemoryCompiler.java | 27 ++--- 4 files changed, 86 insertions(+), 75 deletions(-) diff --git a/src/main/java/run/myCode/compiler/InMemoryJavaFileObject.java b/src/main/java/run/myCode/compiler/InMemoryJavaFileObject.java index 022f8c0..08a4af5 100644 --- a/src/main/java/run/myCode/compiler/InMemoryJavaFileObject.java +++ b/src/main/java/run/myCode/compiler/InMemoryJavaFileObject.java @@ -34,7 +34,11 @@ public InMemoryJavaFileObject(String fileName, String contents) { // Using a non-file scheme is important because ECJ will otherwise try // to open the path returned by getName() from the filesystem which // causes "File ... is missing" errors when compiling in memory. - super(URI.create("mem:///" + fileName), Kind.SOURCE); + // Use the "string" URI scheme which the Eclipse compiler treats as an + // in-memory source and therefore does not attempt to resolve on the + // filesystem. Any other scheme will cause ECJ to verify the file on + // disk and emit a "File ... is missing" error. + super(URI.create("string:///" + fileName), Kind.SOURCE); // Save the file's contents this.contents = contents; @@ -57,15 +61,6 @@ public InputStream openInputStream() throws IOException { return new ByteArrayInputStream(contents.getBytes(StandardCharsets.UTF_8)); } - @Override - public String getName() { - String path = toUri().getPath(); - if (path.startsWith("/")) { - return path.substring(1); - } - return path; - } - @Override public String toString() { String s = this.getName() + ":\n"; diff --git a/src/main/java/run/myCode/compiler/JavaCodeCompiler.java b/src/main/java/run/myCode/compiler/JavaCodeCompiler.java index f98295e..5e14b44 100644 --- a/src/main/java/run/myCode/compiler/JavaCodeCompiler.java +++ b/src/main/java/run/myCode/compiler/JavaCodeCompiler.java @@ -17,7 +17,7 @@ import javax.tools.JavaFileObject; import javax.tools.StandardJavaFileManager; -import org.eclipse.jdt.internal.compiler.tool.EclipseCompiler; +import javax.tools.ToolProvider; public class JavaCodeCompiler { @@ -56,7 +56,10 @@ public static FromMemoryClassLoader compile(Iterable f final FromMemoryClassLoader classLoader = new FromMemoryClassLoader(urlcl); // get system compiler: - final JavaCompiler compiler = new EclipseCompiler(); + // Use the standard Java compiler provided by the JDK. This avoids ECJ + // attempting to resolve in-memory sources on disk which resulted in + // "File ... is missing" errors during tests. + final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); // create a diagnostic listener for compilation diagnostic message processing on // compilation WARNING/ERROR @@ -76,11 +79,11 @@ public static FromMemoryClassLoader compile(Iterable f options = new ArrayList<>(); } - // Build the classpath from the current folder and the system classpath - StringBuilder classpathBuilder = - new StringBuilder("." - + System.getProperty("path.separator") - + System.getProperty("java.class.path")); + // Build the classpath from the current folder and the system classpath + StringBuilder classpathBuilder = + new StringBuilder("." + + System.getProperty("path.separator") + + System.getProperty("java.class.path")); // Add any included jar files from the lib folder to the classpath (wildcard isn't working) try { diff --git a/src/main/java/zss/compiler/InMemoryJavaFileObject.java b/src/main/java/zss/compiler/InMemoryJavaFileObject.java index 4e21c15..838f98f 100644 --- a/src/main/java/zss/compiler/InMemoryJavaFileObject.java +++ b/src/main/java/zss/compiler/InMemoryJavaFileObject.java @@ -1,46 +1,56 @@ -package zss.compiler; - -import java.io.IOException; -import java.net.URI; - -import javax.tools.SimpleJavaFileObject; - -import javax.tools.JavaFileObject.Kind; - -/** - * java File Object represents an in-memory java source file so there is no need - * to put the source file on hard disk - */ -@SuppressWarnings("unused") -public class InMemoryJavaFileObject extends SimpleJavaFileObject { - - private String contents = null; - - /** - * Create a Java file object in memory with a name and text contents. - * - * @param fileName the name of the file, with extension - * @param contents the contents of the file as a single string object - * @throws Exception - */ - public InMemoryJavaFileObject(String fileName, String contents) { - // Create a file object with a classname instead of a filename by - // removing the file's extension and convert the . separators into slashes - super(URI.create("file:///" + fileName), Kind.SOURCE); - - // Save the file's contents - this.contents = contents; - } - - @Override - public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { - return contents; - } - - public String toString() { - String s = this.getName() + ":\n"; - s += this.contents; - - return s; - } -} +package zss.compiler; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.nio.charset.StandardCharsets; + +import javax.tools.SimpleJavaFileObject; + +import javax.tools.JavaFileObject.Kind; + +/** + * java File Object represents an in-memory java source file so there is no need + * to put the source file on hard disk + */ +@SuppressWarnings("unused") +public class InMemoryJavaFileObject extends SimpleJavaFileObject { + + private String contents = null; + + /** + * Create a Java file object in memory with a name and text contents. + * + * @param fileName the name of the file, with extension + * @param contents the contents of the file as a single string object + */ + public InMemoryJavaFileObject(String fileName, String contents) { + // Use the "string" URI scheme so the compiler treats this source as an + // in-memory file and does not attempt to resolve it on disk. Using the + // "file" scheme caused ECJ to look for a physical file and fail with + // "File ... is missing" errors. + super(URI.create("string:///" + fileName), Kind.SOURCE); + + // Save the file's contents + this.contents = contents; + } + + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { + return contents; + } + + @Override + public InputStream openInputStream() throws IOException { + return new ByteArrayInputStream(contents.getBytes(StandardCharsets.UTF_8)); + } + + @Override + public String toString() { + String s = this.getName() + ":\n"; + s += this.contents; + + return s; + } +} diff --git a/src/main/java/zss/compiler/MemoryCompiler.java b/src/main/java/zss/compiler/MemoryCompiler.java index 45ab749..5014082 100644 --- a/src/main/java/zss/compiler/MemoryCompiler.java +++ b/src/main/java/zss/compiler/MemoryCompiler.java @@ -12,13 +12,14 @@ import javax.tools.JavaFileObject; import javax.tools.StandardJavaFileManager; -import org.eclipse.jdt.internal.compiler.tool.EclipseCompiler; +import javax.tools.ToolProvider; -/** - * An instance of the Eclipse Compiler for Java that compiles memory files into - * memory byte code - */ -public class MemoryCompiler { +/** + * Compiles in-memory source files into byte code using the standard JDK + * compiler. This avoids relying on the Eclipse compiler which attempted to + * resolve sources on disk. + */ +public class MemoryCompiler { /** * Compile Java source files into memory * @@ -36,8 +37,10 @@ public static FromMemoryClassLoader compile(Iterable f // classloader final FromMemoryClassLoader classLoader = new FromMemoryClassLoader(urlcl); - // get system compiler: - final JavaCompiler compiler = new EclipseCompiler(); + // Use the standard JDK compiler to avoid ECJ attempting to read + // temporary files from disk which caused "File ... is missing" + // errors when compiling in memory. + final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); // create a diagnostic listener for compilation diagnostic message processing on // compilation WARNING/ERROR @@ -48,10 +51,10 @@ public static FromMemoryClassLoader compile(Iterable f // specify options for compiler List options = new ArrayList<>(); - options.addAll( - Arrays.asList("-classpath", MemoryCompiler.class.getProtectionDomain().getCodeSource().getLocation() - + ":" + System.getProperty("java.class.path"))); - options.addAll(Arrays.asList("-1.8", "-nowarn")); + options.addAll( + Arrays.asList("-classpath", MemoryCompiler.class.getProtectionDomain().getCodeSource().getLocation() + + ":" + System.getProperty("java.class.path"))); + options.addAll(Arrays.asList("--release", "8", "-nowarn")); Writer out = new PrintWriter(System.out); JavaCompiler.CompilationTask task = compiler.getTask(out, fileManager, diag, options, null, files); From 27df92a84a3a65f64373e399015262992cfa54a6 Mon Sep 17 00:00:00 2001 From: Brian Dahlem Date: Wed, 3 Sep 2025 15:47:18 -0700 Subject: [PATCH 10/13] feat: update project to Java 17 --- dependency-reduced-pom.xml | 50 +++---------------- nb-configuration.xml | 6 +-- pom.xml | 47 ++++------------- .../run/myCode/compiler/JavaCodeCompiler.java | 6 +-- .../java/zss/compiler/MemoryCompiler.java | 2 +- 5 files changed, 26 insertions(+), 85 deletions(-) diff --git a/dependency-reduced-pom.xml b/dependency-reduced-pom.xml index 12fc0d0..bc81d14 100644 --- a/dependency-reduced-pom.xml +++ b/dependency-reduced-pom.xml @@ -6,30 +6,9 @@ 4.0.0 - - maven-install-plugin - - - hack-binary - clean - - install-file - - - ${JAVA_HOME}/lib/tools.jar - default - com.sun - tools - jdk1.8.0 - jar - true - - - - - - maven-shade-plugin - 3.0.0 + + maven-shade-plugin + 3.0.0 package @@ -44,20 +23,7 @@ - - - windows-profile - - ${JAVA_HOME}/lib/tools.jar - - - - mac-profile - - ${java.home}/../lib/tools.jar - - - + junit @@ -73,8 +39,8 @@ - 1.8 - 1.8 - - + 17 + 17 + + diff --git a/nb-configuration.xml b/nb-configuration.xml index 15dea5e..469d086 100644 --- a/nb-configuration.xml +++ b/nb-configuration.xml @@ -13,6 +13,6 @@ You can copy and paste the single properties, into the pom.xml file and the IDE That way multiple projects can share the same settings (useful for formatting rules for example). Any value defined here will override the pom.xml file value but is only applicable to the current project. --> - JDK_1.8 - - + JDK_17 + + diff --git a/pom.xml b/pom.xml index aef3cf6..e1694cc 100644 --- a/pom.xml +++ b/pom.xml @@ -4,10 +4,11 @@ hellofunction 6.0.1 - 1.8 - 1.8 - UTF-8 - + 17 + 17 + 17 + UTF-8 + cagatay-gurturk @@ -19,32 +20,7 @@ https://repo.eclipse.org/content/repositories/eclipse-staging/ --> - - - windows-profile - - true - - ${JAVA_HOME}/lib/tools.jar - - - - ${JAVA_HOME}/lib/tools.jar - - - - mac-profile - - false - - ${java.home}/../lib/tools.jar - - - - ${java.home}/../lib/tools.jar - - - + @@ -93,12 +69,11 @@ --> - - org.eclipse.jdt - ecj - (,3.13.0] - - + + org.eclipse.jdt + ecj + 3.42.0 + junit diff --git a/src/main/java/run/myCode/compiler/JavaCodeCompiler.java b/src/main/java/run/myCode/compiler/JavaCodeCompiler.java index 861d0d1..46a256d 100644 --- a/src/main/java/run/myCode/compiler/JavaCodeCompiler.java +++ b/src/main/java/run/myCode/compiler/JavaCodeCompiler.java @@ -95,9 +95,9 @@ public static FromMemoryClassLoader compile(Iterable f // System.out.println(">> " + classpath + " <<"); // Set the classpath and java version for the compiler - options.addAll(Arrays.asList("-classpath", - classpath)); - options.addAll(Arrays.asList("-1.8")); + options.addAll(Arrays.asList("-classpath", + classpath)); + options.addAll(Arrays.asList("-17")); Writer out = new PrintWriter(System.out); diff --git a/src/main/java/zss/compiler/MemoryCompiler.java b/src/main/java/zss/compiler/MemoryCompiler.java index 45ab749..25a0084 100644 --- a/src/main/java/zss/compiler/MemoryCompiler.java +++ b/src/main/java/zss/compiler/MemoryCompiler.java @@ -51,7 +51,7 @@ public static FromMemoryClassLoader compile(Iterable f options.addAll( Arrays.asList("-classpath", MemoryCompiler.class.getProtectionDomain().getCodeSource().getLocation() + ":" + System.getProperty("java.class.path"))); - options.addAll(Arrays.asList("-1.8", "-nowarn")); + options.addAll(Arrays.asList("-17", "-nowarn")); Writer out = new PrintWriter(System.out); JavaCompiler.CompilationTask task = compiler.getTask(out, fileManager, diag, options, null, files); From dc79544a0eb3fed8232e21d390b99841c5ed601a Mon Sep 17 00:00:00 2001 From: Brian Dahlem Date: Wed, 3 Sep 2025 15:56:02 -0700 Subject: [PATCH 11/13] chore: configure Java 17 in devcontainer and CI --- .devcontainer/devcontainer.json | 6 ++++++ .github/workflows/codeql-analysis.yml | 6 ++++++ .github/workflows/maven-publish.yml | 7 ++++--- .github/workflows/maven.yml | 7 ++++--- 4 files changed, 20 insertions(+), 6 deletions(-) create mode 100644 .devcontainer/devcontainer.json diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..873a882 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,6 @@ +{ + "name": "Java 17 Dev Container", + "image": "mcr.microsoft.com/devcontainers/java:17", + "settings": {}, + "extensions": [] +} diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index e935a91..ab20f69 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -37,6 +37,12 @@ jobs: - name: Checkout repository uses: actions/checkout@v2 + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: '17' + # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v1 diff --git a/.github/workflows/maven-publish.yml b/.github/workflows/maven-publish.yml index 5de62b4..37e2eb5 100644 --- a/.github/workflows/maven-publish.yml +++ b/.github/workflows/maven-publish.yml @@ -14,10 +14,11 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Set up JDK 1.8 - uses: actions/setup-java@v1 + - name: Set up JDK 17 + uses: actions/setup-java@v3 with: - java-version: 1.8 + distribution: 'temurin' + java-version: '17' server-id: github # Value of the distributionManagement/repository/id field of the pom.xml settings-path: ${{ github.workspace }} # location for the settings.xml file diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index c8aeb3a..ae9ed8c 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -16,9 +16,10 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Set up JDK 1.8 - uses: actions/setup-java@v1 + - name: Set up JDK 17 + uses: actions/setup-java@v3 with: - java-version: 1.8 + distribution: 'temurin' + java-version: '17' - name: Build with Maven run: mvn -B package --file pom.xml From 9a63f5fa013f128073abff758eb9a566ed020af8 Mon Sep 17 00:00:00 2001 From: Brian Dahlem Date: Wed, 3 Sep 2025 23:23:24 +0000 Subject: [PATCH 12/13] fixed devcontainer to include maven, compiler to use release java 17 --- .devcontainer/devcontainer.json | 7 +++++++ src/main/java/run/myCode/compiler/JavaCodeCompiler.java | 2 +- src/main/java/zss/compiler/MemoryCompiler.java | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 873a882..945c9cb 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,6 +1,13 @@ { "name": "Java 17 Dev Container", "image": "mcr.microsoft.com/devcontainers/java:17", + "features": { + "ghcr.io/devcontainers/features/java:1": { + "version": "none", + "installGradle": "false", + "installMaven": "true" + } + }, "settings": {}, "extensions": [] } diff --git a/src/main/java/run/myCode/compiler/JavaCodeCompiler.java b/src/main/java/run/myCode/compiler/JavaCodeCompiler.java index 22a3f47..09cdbb1 100644 --- a/src/main/java/run/myCode/compiler/JavaCodeCompiler.java +++ b/src/main/java/run/myCode/compiler/JavaCodeCompiler.java @@ -108,7 +108,7 @@ public static FromMemoryClassLoader compile(Iterable f // Set the classpath and java version for the compiler options.addAll(Arrays.asList("-classpath", classpath)); - options.addAll(Arrays.asList("-17")); + options.addAll(Arrays.asList("--release", "17")); Writer out = new PrintWriter(System.out); diff --git a/src/main/java/zss/compiler/MemoryCompiler.java b/src/main/java/zss/compiler/MemoryCompiler.java index 31a4f30..6787a0f 100644 --- a/src/main/java/zss/compiler/MemoryCompiler.java +++ b/src/main/java/zss/compiler/MemoryCompiler.java @@ -54,7 +54,7 @@ public static FromMemoryClassLoader compile(Iterable f options.addAll( Arrays.asList("-classpath", MemoryCompiler.class.getProtectionDomain().getCodeSource().getLocation() + ":" + System.getProperty("java.class.path"))); - options.addAll(Arrays.asList("-17", "-nowarn")); + options.addAll(Arrays.asList("--release", "17", "-nowarn")); Writer out = new PrintWriter(System.out); From 721328e1c21653fa3555ccdbc8e84b9f4f30a240 Mon Sep 17 00:00:00 2001 From: Brian Dahlem Date: Wed, 3 Sep 2025 23:40:44 +0000 Subject: [PATCH 13/13] removed maven repos --- pom.xml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index 39c038d..448acde 100644 --- a/pom.xml +++ b/pom.xml @@ -10,10 +10,11 @@ UTF-8 + @@ -68,12 +69,14 @@ --> + + junit junit