Bug 527260 - [Java 9] need an equivalent option to the javac --release
option

Change-Id: I8e319b20a18cf18c43df29a3ed07a3525b4f9fe8
Signed-off-by: Jay Arthanareeswaran <jarthana@in.ibm.com>
diff --git a/org.eclipse.jdt.compiler.tool.tests/src/org/eclipse/jdt/compiler/tool/tests/CompilerToolJava9Tests.java b/org.eclipse.jdt.compiler.tool.tests/src/org/eclipse/jdt/compiler/tool/tests/CompilerToolJava9Tests.java
index 588a928..3e65152 100644
--- a/org.eclipse.jdt.compiler.tool.tests/src/org/eclipse/jdt/compiler/tool/tests/CompilerToolJava9Tests.java
+++ b/org.eclipse.jdt.compiler.tool.tests/src/org/eclipse/jdt/compiler/tool/tests/CompilerToolJava9Tests.java
@@ -10,32 +10,50 @@
  *******************************************************************************/
 package org.eclipse.jdt.compiler.tool.tests;
 
+import java.io.BufferedWriter;
+import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileOutputStream;
+import java.io.FileWriter;
 import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.net.URL;
 import java.nio.charset.Charset;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 import java.util.Locale;
 import java.util.ServiceLoader;
 
 import javax.lang.model.SourceVersion;
+import javax.tools.Diagnostic;
+import javax.tools.FileObject;
+import javax.tools.ForwardingJavaFileManager;
 import javax.tools.JavaCompiler;
 import javax.tools.JavaFileManager;
+import javax.tools.JavaFileObject;
 import javax.tools.JavaFileManager.Location;
+import javax.tools.JavaFileObject.Kind;
 import javax.tools.StandardJavaFileManager;
 import javax.tools.StandardLocation;
 import javax.tools.ToolProvider;
+import javax.tools.JavaCompiler.CompilationTask;
 
 import org.eclipse.core.runtime.FileLocator;
 import org.eclipse.core.runtime.Platform;
+import org.eclipse.jdt.compiler.tool.tests.AbstractCompilerToolTest.CompilerInvocationDiagnosticListener;
+import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
+import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader;
+import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException;
 import org.eclipse.jdt.internal.compiler.tool.EclipseCompiler;
 
 import junit.framework.TestCase;
 
 public class CompilerToolJava9Tests extends TestCase {
+	private static final boolean DEBUG = false;
 	private static final String RESOURCES_DIR = "resources";
 	private JavaCompiler[] compilers;
 	private String[] compilerNames;
@@ -153,6 +171,306 @@
 			}
 		}
 	}
+	public void testOptionRelease1() {
+		if (this.isJREBelow9) return;
+		JavaCompiler compiler = this.compilers[1];
+		String tmpFolder = System.getProperty("java.io.tmpdir");
+		File inputFile = new File(tmpFolder, "X.java");
+		BufferedWriter writer = null;
+		try {
+			writer = new BufferedWriter(new FileWriter(inputFile));
+			writer.write(
+				"package p;\n" +
+				"public class X {}");
+			writer.flush();
+			writer.close();
+		} catch (IOException e) {
+			// ignore
+		} finally {
+			if (writer != null) {
+				try {
+					writer.close();
+				} catch (IOException e) {
+					// ignore
+				}
+			}
+		}
+		StandardJavaFileManager manager = compiler.getStandardFileManager(null, Locale.getDefault(), Charset.defaultCharset());
+
+		ForwardingJavaFileManager<StandardJavaFileManager> forwardingJavaFileManager = new ForwardingJavaFileManager<StandardJavaFileManager>(manager) {
+			@Override
+			public FileObject getFileForInput(Location location, String packageName, String relativeName)
+					throws IOException {
+				if (DEBUG) {
+					System.out.println("Create file for input : " + packageName + " " + relativeName + " in location " + location);
+				}
+				return super.getFileForInput(location, packageName, relativeName);
+			}
+			@Override
+			public JavaFileObject getJavaFileForInput(Location location, String className, Kind kind)
+					throws IOException {
+				if (DEBUG) {
+					System.out.println("Create java file for input : " + className + " in location " + location);
+				}
+				return super.getJavaFileForInput(location, className, kind);
+			}
+			@Override
+			public JavaFileObject getJavaFileForOutput(Location location,
+					String className,
+					Kind kind,
+					FileObject sibling) throws IOException {
+
+				if (DEBUG) {
+					System.out.println("Create .class file for " + className + " in location " + location + " with sibling " + sibling.toUri());
+				}
+				JavaFileObject javaFileForOutput = super.getJavaFileForOutput(location, className, kind, sibling);
+				if (DEBUG) {
+					System.out.println(javaFileForOutput.toUri());
+				}
+				return javaFileForOutput;
+			}
+		};
+		// create new list containing input file
+		List<File> files = new ArrayList<File>();
+		files.add(inputFile);
+		Iterable<? extends JavaFileObject> units = manager.getJavaFileObjectsFromFiles(files);
+		StringWriter stringWriter = new StringWriter();
+		PrintWriter printWriter = new PrintWriter(stringWriter);
+
+		List<String> options = new ArrayList<String>();
+		options.add("-d");
+		options.add(tmpFolder);
+		options.add("--release");
+		options.add("8");
+		ByteArrayOutputStream errBuffer = new ByteArrayOutputStream();
+		PrintWriter err = new PrintWriter(errBuffer);
+		CompilerInvocationDiagnosticListener listener = new CompilerInvocationDiagnosticListener(err) {
+			@Override
+			public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
+				JavaFileObject source = diagnostic.getSource();
+				assertNotNull("No source", source);
+				super.report(diagnostic);
+			}
+		};
+ 		CompilationTask task = compiler.getTask(printWriter, forwardingJavaFileManager, listener, options, null, units);
+ 		// check the classpath location
+ 		assertTrue("Has no location CLASS_OUPUT", forwardingJavaFileManager.hasLocation(StandardLocation.CLASS_OUTPUT));
+		Boolean result = task.call();
+		printWriter.flush();
+		printWriter.close();
+ 		if (!result.booleanValue()) {
+ 			System.err.println("Compilation failed: " + stringWriter.getBuffer().toString());
+ 	 		assertTrue("Compilation failed ", false);
+ 		}
+ 		ClassFileReader reader = null;
+ 		try {
+			reader = ClassFileReader.read(new File(tmpFolder, "p/X.class"), true);
+		} catch (ClassFormatException e) {
+			assertTrue("Should not happen", false);
+		} catch (IOException e) {
+			assertTrue("Should not happen", false);
+		}
+		assertNotNull("No reader", reader);
+		// This needs fix. This test case by design will produce different output every compiler version.
+ 		assertEquals("Wrong value", ClassFileConstants.JDK1_8, reader.getVersion());
+		// check that the .class file exist for X
+		assertTrue("delete failed", inputFile.delete());
+	}
+	public void testOptionRelease2() {
+		if (this.isJREBelow9) return;
+		JavaCompiler compiler = this.compilers[1];
+		String tmpFolder = System.getProperty("java.io.tmpdir");
+		File inputFile = new File(tmpFolder, "X.java");
+		BufferedWriter writer = null;
+		try {
+			writer = new BufferedWriter(new FileWriter(inputFile));
+			writer.write(
+				"package p;\n" +
+				"public class X {}");
+			writer.flush();
+			writer.close();
+		} catch (IOException e) {
+			// ignore
+		} finally {
+			if (writer != null) {
+				try {
+					writer.close();
+				} catch (IOException e) {
+					// ignore
+				}
+			}
+		}
+		StandardJavaFileManager manager = compiler.getStandardFileManager(null, Locale.getDefault(), Charset.defaultCharset());
+
+		ForwardingJavaFileManager<StandardJavaFileManager> forwardingJavaFileManager = new ForwardingJavaFileManager<StandardJavaFileManager>(manager) {
+			@Override
+			public FileObject getFileForInput(Location location, String packageName, String relativeName)
+					throws IOException {
+				if (DEBUG) {
+					System.out.println("Create file for input : " + packageName + " " + relativeName + " in location " + location);
+				}
+				return super.getFileForInput(location, packageName, relativeName);
+			}
+			@Override
+			public JavaFileObject getJavaFileForInput(Location location, String className, Kind kind)
+					throws IOException {
+				if (DEBUG) {
+					System.out.println("Create java file for input : " + className + " in location " + location);
+				}
+				return super.getJavaFileForInput(location, className, kind);
+			}
+			@Override
+			public JavaFileObject getJavaFileForOutput(Location location,
+					String className,
+					Kind kind,
+					FileObject sibling) throws IOException {
+
+				if (DEBUG) {
+					System.out.println("Create .class file for " + className + " in location " + location + " with sibling " + sibling.toUri());
+				}
+				JavaFileObject javaFileForOutput = super.getJavaFileForOutput(location, className, kind, sibling);
+				if (DEBUG) {
+					System.out.println(javaFileForOutput.toUri());
+				}
+				return javaFileForOutput;
+			}
+		};
+		// create new list containing input file
+		List<File> files = new ArrayList<File>();
+		files.add(inputFile);
+		Iterable<? extends JavaFileObject> units = manager.getJavaFileObjectsFromFiles(files);
+		StringWriter stringWriter = new StringWriter();
+		PrintWriter printWriter = new PrintWriter(stringWriter);
+
+		List<String> options = new ArrayList<String>();
+		options.add("-d");
+		options.add(tmpFolder);
+		options.add("--release");
+		options.add("8");
+		ByteArrayOutputStream errBuffer = new ByteArrayOutputStream();
+		PrintWriter err = new PrintWriter(errBuffer);
+		CompilerInvocationDiagnosticListener listener = new CompilerInvocationDiagnosticListener(err) {
+			@Override
+			public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
+				JavaFileObject source = diagnostic.getSource();
+				assertNotNull("No source", source);
+				super.report(diagnostic);
+			}
+		};
+ 		CompilationTask task = compiler.getTask(printWriter, forwardingJavaFileManager, listener, options, null, units);
+ 		// check the classpath location
+ 		assertTrue("Has no location CLASS_OUPUT", forwardingJavaFileManager.hasLocation(StandardLocation.CLASS_OUTPUT));
+		Boolean result = task.call();
+		printWriter.flush();
+		printWriter.close();
+ 		if (!result.booleanValue()) {
+ 			System.err.println("Compilation failed: " + stringWriter.getBuffer().toString());
+ 	 		assertTrue("Compilation failed ", false);
+ 		}
+ 		ClassFileReader reader = null;
+ 		try {
+			reader = ClassFileReader.read(new File(tmpFolder, "p/X.class"), true);
+		} catch (ClassFormatException e) {
+			assertTrue("Should not happen", false);
+		} catch (IOException e) {
+			assertTrue("Should not happen", false);
+		}
+		assertNotNull("No reader", reader);
+		// This needs fix. This test case by design will produce different output every compiler version.
+ 		assertEquals("Wrong value", ClassFileConstants.JDK1_8, reader.getVersion());
+		// check that the .class file exist for X
+		assertTrue("delete failed", inputFile.delete());
+	}
+	public void testOptionRelease3() {
+		if (this.isJREBelow9) return;
+		JavaCompiler compiler = this.compilers[1];
+		String tmpFolder = System.getProperty("java.io.tmpdir");
+		File inputFile = new File(tmpFolder, "X.java");
+		BufferedWriter writer = null;
+		try {
+			writer = new BufferedWriter(new FileWriter(inputFile));
+			writer.write(
+				"package p;\n" +
+				"public class X {}");
+			writer.flush();
+			writer.close();
+		} catch (IOException e) {
+			// ignore
+		} finally {
+			if (writer != null) {
+				try {
+					writer.close();
+				} catch (IOException e) {
+					// ignore
+				}
+			}
+		}
+		StandardJavaFileManager manager = compiler.getStandardFileManager(null, Locale.getDefault(), Charset.defaultCharset());
+
+		ForwardingJavaFileManager<StandardJavaFileManager> forwardingJavaFileManager = new ForwardingJavaFileManager<StandardJavaFileManager>(manager) {
+			@Override
+			public FileObject getFileForInput(Location location, String packageName, String relativeName)
+					throws IOException {
+				if (DEBUG) {
+					System.out.println("Create file for input : " + packageName + " " + relativeName + " in location " + location);
+				}
+				return super.getFileForInput(location, packageName, relativeName);
+			}
+			@Override
+			public JavaFileObject getJavaFileForInput(Location location, String className, Kind kind)
+					throws IOException {
+				if (DEBUG) {
+					System.out.println("Create java file for input : " + className + " in location " + location);
+				}
+				return super.getJavaFileForInput(location, className, kind);
+			}
+			@Override
+			public JavaFileObject getJavaFileForOutput(Location location,
+					String className,
+					Kind kind,
+					FileObject sibling) throws IOException {
+
+				if (DEBUG) {
+					System.out.println("Create .class file for " + className + " in location " + location + " with sibling " + sibling.toUri());
+				}
+				JavaFileObject javaFileForOutput = super.getJavaFileForOutput(location, className, kind, sibling);
+				if (DEBUG) {
+					System.out.println(javaFileForOutput.toUri());
+				}
+				return javaFileForOutput;
+			}
+		};
+		// create new list containing input file
+		List<File> files = new ArrayList<File>();
+		files.add(inputFile);
+		Iterable<? extends JavaFileObject> units = manager.getJavaFileObjectsFromFiles(files);
+		StringWriter stringWriter = new StringWriter();
+		PrintWriter printWriter = new PrintWriter(stringWriter);
+
+		List<String> options = new ArrayList<String>();
+		options.add("-d");
+		options.add(tmpFolder);
+		options.add("--release");
+		options.add("8");
+		options.add("-source");
+		options.add("7");
+		ByteArrayOutputStream errBuffer = new ByteArrayOutputStream();
+		PrintWriter err = new PrintWriter(errBuffer);
+		CompilerInvocationDiagnosticListener listener = new CompilerInvocationDiagnosticListener(err) {
+			@Override
+			public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
+				JavaFileObject source = diagnostic.getSource();
+				assertNotNull("No source", source);
+				super.report(diagnostic);
+			}
+		};
+		try {
+			compiler.getTask(printWriter, forwardingJavaFileManager, listener, options, null, units);
+			fail("compilation didn't fail as expected");
+		} catch(IllegalArgumentException iae) {
+			assertEquals("option -source is not supported when --release is used", iae.getMessage());
+		}
+	}
 	public void testGetJavaFileObjects() {
 		if (this.isJREBelow9) return;
 	}
diff --git a/org.eclipse.jdt.compiler.tool/src/org/eclipse/jdt/internal/compiler/batch/ClasspathJsr199.java b/org.eclipse.jdt.compiler.tool/src/org/eclipse/jdt/internal/compiler/batch/ClasspathJsr199.java
index 4ea2d16..0e11aad 100644
--- a/org.eclipse.jdt.compiler.tool/src/org/eclipse/jdt/internal/compiler/batch/ClasspathJsr199.java
+++ b/org.eclipse.jdt.compiler.tool/src/org/eclipse/jdt/internal/compiler/batch/ClasspathJsr199.java
@@ -27,6 +27,7 @@
 import javax.tools.JavaFileObject;
 
 import org.eclipse.jdt.core.compiler.CharOperation;
+import org.eclipse.jdt.internal.compiler.batch.FileSystem.Classpath;
 import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader;
 import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException;
 import org.eclipse.jdt.internal.compiler.env.IModule;
@@ -42,19 +43,29 @@
 
 	private JavaFileManager fileManager;
 	private JavaFileManager.Location location;
-	private ClasspathJrt jrt;
+	private Classpath jrt;
 
 	public ClasspathJsr199(JavaFileManager file, JavaFileManager.Location location) {
 		super(null, null);
 		this.fileManager = file;
 		this.location = location;
 	}
-	public ClasspathJsr199(ClasspathJrt jrt, JavaFileManager file, JavaFileManager.Location location) {
+	public ClasspathJsr199(Classpath jrt, JavaFileManager file, JavaFileManager.Location location) {
 		super(null, null);
 		this.fileManager = file;
 		this.jrt = jrt;
 		this.location = location;
 	}
+	/*
+	 * Maintain two separate constructors to avoid this being constructed with any other kind of classpath
+	 * (other than ClasspathJrt and ClasspathJep249
+	 */
+	public ClasspathJsr199(ClasspathJep247 older, JavaFileManager file, JavaFileManager.Location location) {
+		super(null, null);
+		this.fileManager = file;
+		this.jrt = older;
+		this.location = location;
+	}
 
 	@Override
 	public List fetchLinkedJars(FileSystem.ClasspathSectionProblemReporter problemReporter) {
@@ -142,7 +153,9 @@
 
 	@Override
 	public void initialize() throws IOException {
-		// nothing to do
+		if (this.jrt != null) {
+			this.jrt.initialize();
+		}
 	}
 
 	@Override
@@ -195,6 +208,9 @@
 		} catch (IOException e) {
 			// ignore
 		}
+		if (this.jrt != null) {
+			this.jrt.reset();
+		}
 	}
 
 	@Override
diff --git a/org.eclipse.jdt.compiler.tool/src/org/eclipse/jdt/internal/compiler/tool/EclipseCompilerImpl.java b/org.eclipse.jdt.compiler.tool/src/org/eclipse/jdt/internal/compiler/tool/EclipseCompilerImpl.java
index cb5e9e7..b77335f 100644
--- a/org.eclipse.jdt.compiler.tool/src/org/eclipse/jdt/internal/compiler/tool/EclipseCompilerImpl.java
+++ b/org.eclipse.jdt.compiler.tool/src/org/eclipse/jdt/internal/compiler/tool/EclipseCompilerImpl.java
@@ -55,6 +55,7 @@
 import org.eclipse.jdt.internal.compiler.batch.FileSystem.Classpath;
 import org.eclipse.jdt.internal.compiler.batch.Main;
 import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
+import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
 import org.eclipse.jdt.internal.compiler.lookup.TypeConstants;
 import org.eclipse.jdt.internal.compiler.problem.AbortCompilationUnit;
 import org.eclipse.jdt.internal.compiler.problem.DefaultProblem;
@@ -71,7 +72,6 @@
 	Iterable<? extends JavaFileObject> compilationUnits;
 	public JavaFileManager fileManager;
 	protected Processor[] processors;
-	// TODO: This is not yet used anywhere
 	public DiagnosticListener<? super JavaFileObject> diagnosticListener;
 
 	public EclipseCompilerImpl(PrintWriter out, PrintWriter err, boolean systemExitWhenFinished) {
@@ -472,6 +472,7 @@
 			String customEncoding) {
 		// Sometimes this gets called too early there by losing locations set after that point.
 		// The code is now moved to handleLocations() which is invoked just before compilation
+		validateClasspathOptions(bootclasspaths, endorsedDirClasspaths, extdirsClasspaths);
 	}
 
 	protected void handleLocations() {
@@ -531,9 +532,20 @@
 			}
 		} else if (javaFileManager != null) {
 			File javaHome = Util.getJavaHome();
-			if (Util.getJDKLevel(javaHome) >= ClassFileConstants.JDK9) { 
-				ClasspathJrt jrt = (ClasspathJrt) FileSystem.getJrtClasspath(javaHome.toString(), null, null, null);
-				Classpath classpath = new ClasspathJsr199(jrt, this.fileManager, StandardLocation.PLATFORM_CLASS_PATH);
+			long jdkLevel = Util.getJDKLevel(javaHome);
+			if (jdkLevel >= ClassFileConstants.JDK9) {
+				Classpath system = null;
+				if (this.releaseVersion != null && this.complianceLevel < jdkLevel) {
+					String versionFromJdkLevel = CompilerOptions.versionFromJdkLevel(this.complianceLevel);
+					if (versionFromJdkLevel.length() >= 3) {
+						versionFromJdkLevel = versionFromJdkLevel.substring(2);
+					}
+					// TODO: Revisit for access rules
+					system = FileSystem.getOlderSystemRelease(javaHome.getAbsolutePath(), versionFromJdkLevel, null);
+				} else {
+					system = FileSystem.getJrtClasspath(javaHome.toString(), null, null, null);
+				}						
+				Classpath classpath = new ClasspathJsr199(system, this.fileManager, StandardLocation.PLATFORM_CLASS_PATH);
 				fileSystemClasspaths.add(classpath);
 			} else {
 				Classpath classpath = new ClasspathJsr199(this.fileManager, StandardLocation.PLATFORM_CLASS_PATH);
diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/BatchCompilerTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/BatchCompilerTest.java
index 1b7c6e6..206c2bc 100644
--- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/BatchCompilerTest.java
+++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/BatchCompilerTest.java
@@ -44,6 +44,8 @@
 import java.util.Iterator;
 import java.util.List;
 
+import javax.lang.model.SourceVersion;
+
 import junit.framework.Test;
 
 import org.eclipse.jdt.core.JavaCore;
@@ -630,7 +632,21 @@
         "                       specify where to find source files for multiple modules\n" +
         "    -p --module-path <directories separated by " + File.pathSeparator + ">\n" +
         "                       specify where to find application modules\n" +
-        "    --system <jdk>      Override location of system modules \n" +
+        "    --system <jdk>      Override location of system modules\n" +
+        "    --add-exports <module>/<package>=<other-module>(,<other-module>)*\n" +
+        "                       specify additional package exports clauses to the\n" +
+        "                       given modules\n" +
+        "    --add-reads <module>=<other-module>(,<other-module>)*\n" +
+        "                       specify additional modules to be considered as required\n" +
+        "                       by given modules\n" +
+        "    --add-modules  <module>(,<module>)*\n" +
+        "                       specify the additional module names that should be\n" +
+        "                       resolved to be root modules\n" +
+        "    --limit-modules <module>(,<module>)*\n" +
+        "                       specify the observable module names\n" +
+        "    --release <release>\n" +
+        "                       compile for a specific VM version\n" +
+        " \n" +
         " Compliance options:\n" +
         "    -1.3               use 1.3 compliance (-source 1.3 -target 1.1)\n" +
         "    -1.4             + use 1.4 compliance (-source 1.3 -target 1.2)\n" +
@@ -13040,4 +13056,25 @@
 		"",
 		true);
 }
+public void testReleaseOption() throws Exception {
+	try {
+		SourceVersion valueOf = SourceVersion.valueOf("RELEASE_9");
+		if (valueOf != null)
+			return;
+	} catch(IllegalArgumentException iae) {
+		// Ignore
+	}
+	this.runNegativeTest(
+			new String[] {
+				"X.java",
+				"/** */\n" +
+				"public class X {\n" +
+				"}",
+			},
+	     "\"" + OUTPUT_DIR +  File.separator + "X.java\""
+	     + " --release 8 -d \"" + OUTPUT_DIR + "\"",
+	     "",
+	     "option --release is supported only when run with JDK 9 or above\n",
+	     true);
+}
 }
diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/ModuleCompilationTests.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/ModuleCompilationTests.java
index db94630..95fc5da 100644
--- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/ModuleCompilationTests.java
+++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/ModuleCompilationTests.java
@@ -3661,4 +3661,717 @@
 			false,
 			OUTPUT_DIR + File.separator + out);
 	}
+	/*
+	 * Test that when module-info is the only file being compiled, the class is still
+	 * generated inside the module's sub folder.
+	 */
+	public void testBug500170a() {
+		File outputDirectory = new File(OUTPUT_DIR);
+		Util.flushDirectoryContent(outputDirectory);
+		String out = "bin";
+		String directory = OUTPUT_DIR + File.separator + "src";
+		String moduleLoc = directory + File.separator + "mod.one";
+		List<String> files = new ArrayList<>(); 
+		writeFileCollecting(files, moduleLoc, "module-info.java", 
+						"module mod.one { \n" +
+						"	requires java.sql;\n" +
+						"}");
+		StringBuffer buffer = new StringBuffer();
+		buffer.append("-d " + OUTPUT_DIR + File.separator + out )
+			.append(" -9 ")
+			.append(" -classpath \"")
+			.append(Util.getJavaClassLibsAsString())
+			.append("\" ")
+			.append(" --module-source-path " + "\"" + directory + "\" ")
+			.append(moduleLoc + File.separator + "module-info.java");
+
+		Set<String> classFiles = runConformModuleTest(
+				new String[] {
+					"mod.one/module-info.java",
+					"module mod.one { \n" +
+					"	requires java.sql;\n" +
+					"}"
+				},
+				buffer.toString(),
+				"",
+				"",
+				false);
+		String fileName = OUTPUT_DIR + File.separator + out + File.separator + "mod.one" + File.separator + "module-info.class";
+		assertClassFile("Missing modul-info.class: " + fileName, fileName, classFiles);
+	}
+	/*
+	 * Test that no NPE is thrown when the module-info is compiled at a level below 9
+	 */
+	public void testBug500170b() {
+		File outputDirectory = new File(OUTPUT_DIR);
+		Util.flushDirectoryContent(outputDirectory);
+		String out = "bin";
+		String directory = OUTPUT_DIR + File.separator + "src";
+		String moduleLoc = directory + File.separator + "mod.one";
+		List<String> files = new ArrayList<>(); 
+		writeFileCollecting(files, moduleLoc, "module-info.java", 
+						"module mod.one { \n" +
+						"	requires java.sql;\n" +
+						"}");
+		StringBuffer buffer = new StringBuffer();
+		buffer.append("-d " + OUTPUT_DIR + File.separator + out )
+			.append(" -classpath \"")
+			.append(Util.getJavaClassLibsAsString())
+			.append("\" ")
+			.append(" --module-source-path " + "\"" + directory + "\"");
+
+		runNegativeModuleTest(files, 
+				buffer,
+				"",
+				"----------\n" + 
+				"1. ERROR in ---OUTPUT_DIR_PLACEHOLDER---/src/mod.one/module-info.java (at line 1)\n" + 
+				"	module mod.one { \n" + 
+				"	^^^^^^\n" + 
+				"Syntax error on token \"module\", package expected\n" + 
+				"----------\n" + 
+				"2. ERROR in ---OUTPUT_DIR_PLACEHOLDER---/src/mod.one/module-info.java (at line 1)\n" + 
+				"	module mod.one { \n" + 
+				"	^^^^^^^^^^^^^^\n" + 
+				"Syntax error on token(s), misplaced construct(s)\n" + 
+				"----------\n" + 
+				"3. ERROR in ---OUTPUT_DIR_PLACEHOLDER---/src/mod.one/module-info.java (at line 2)\n" + 
+				"	requires java.sql;\n" + 
+				"	             ^\n" + 
+				"Syntax error on token \".\", , expected\n" + 
+				"----------\n" + 
+				"4. ERROR in ---OUTPUT_DIR_PLACEHOLDER---/src/mod.one/module-info.java (at line 3)\n" + 
+				"	}\n" + 
+				"	^\n" + 
+				"Syntax error on token \"}\", delete this token\n" + 
+				"----------\n" + 
+				"4 problems (4 errors)\n",
+				false,
+				OUTPUT_DIR + File.separator + out);
+	}
+	public void testBug522472c() {
+		File outputDirectory = new File(OUTPUT_DIR);
+		Util.flushDirectoryContent(outputDirectory);
+		String out = "bin";
+		String directory = OUTPUT_DIR + File.separator + "src";
+		File srcDir = new File(directory);
+		String moduleLoc = directory + File.separator + "mod.one";
+		List<String> files = new ArrayList<>(); 
+		writeFileCollecting(files, moduleLoc, 
+				"module-info.java", 
+				"module mod.one { \n" +
+				"	exports x.y.z;\n" +
+				"	exports a.b.c;\n" +
+				"}");
+		writeFileCollecting(files, moduleLoc + File.separator + "x" + File.separator + "y" + File.separator + "z", 
+				"X.java", 
+				"package x.y.z;\n");
+		writeFileCollecting(files, moduleLoc + File.separator + "a" + File.separator + "b" + File.separator + "c",
+				"A.java",
+				"package a.b.c;\n" +
+				"public class A {}");
+
+		moduleLoc = directory + File.separator + "mod.one.a";
+		writeFileCollecting(files, moduleLoc, 
+				"module-info.java", 
+				"module mod.one.a { \n" +
+				"	exports x.y.z;\n" +
+				"}");
+		writeFileCollecting(files, moduleLoc + File.separator + "x" + File.separator + "y" + File.separator + "z", 
+				"X.java", 
+				"package x.y.z;\n" +
+				"public class X {}\n");
+		StringBuffer buffer = new StringBuffer();
+		buffer.append("-d " + OUTPUT_DIR + File.separator + out )
+			.append(" -9 ")
+			.append(" -classpath \"")
+			.append(Util.getJavaClassLibsAsString())
+			.append("\" ")
+			.append(" --module-source-path " + "\"" + directory + "\" ");
+
+		runConformModuleTest(files,
+				buffer, 
+				"", 
+				"", 
+				false);
+
+		Util.flushDirectoryContent(srcDir);
+		files.clear();
+		moduleLoc = directory + File.separator + "mod.two";
+		writeFileCollecting(files, moduleLoc, 
+				"module-info.java", 
+				"module mod.two { \n" +
+					"	requires mod.one;\n" +
+					"	requires mod.one.a;\n" +
+				"}");
+		writeFileCollecting(files, moduleLoc + File.separator + "p" + File.separator + "q" + File.separator + "r",
+				"Main.java",
+				"package p.q.r;\n" +
+				"import a.b.c.*;\n" +
+				"import x.y.z.*;\n" +
+				"@SuppressWarnings(\"unused\")\n" +
+				"public class Main {"
+				+ "}");
+		buffer = new StringBuffer();
+		buffer.append("-d " + OUTPUT_DIR + File.separator + out )
+			.append(" -9 ")
+			.append(" -classpath \"")
+			.append(Util.getJavaClassLibsAsString())
+			.append("\" ")
+			.append(" --module-path " + "\"" + OUTPUT_DIR + File.separator + out + "\" ")
+			.append(" --module-source-path " + "\"" + directory + "\" ");
+		runConformModuleTest(files, 
+				buffer,
+				"",
+				"",
+				false);
+	}
+	public void testReleaseOption1() throws Exception {
+		this.runConformTest(
+				new String[] {
+					"X.java",
+					"/** */\n" +
+					"public class X {\n" +
+					"}",
+				},
+		     "\"" + OUTPUT_DIR +  File.separator + "X.java\""
+		     + " --release 8 -d \"" + OUTPUT_DIR + "\"",
+		     "",
+		     "",
+		     true);
+		String expectedOutput = "// Compiled from X.java (version 1.8 : 52.0, super bit)";
+			checkDisassembledClassFile(OUTPUT_DIR + File.separator + "X.class", "X", expectedOutput);
+	}
+	public void testReleaseOption2() throws Exception {
+		this.runConformTest(
+				new String[] {
+					"X.java",
+					"/** */\n" +
+					"public class X {\n" +
+					"}",
+				},
+		     "\"" + OUTPUT_DIR +  File.separator + "X.java\""
+		     + " --release 7 -d \"" + OUTPUT_DIR + "\"",
+		     "",
+		     "",
+		     true);
+		String expectedOutput = "// Compiled from X.java (version 1.7 : 51.0, super bit)";
+			checkDisassembledClassFile(OUTPUT_DIR + File.separator + "X.class", "X", expectedOutput);
+	}
+	public void testReleaseOption3() throws Exception {
+		this.runConformTest(
+				new String[] {
+					"X.java",
+					"/** */\n" +
+					"public class X {\n" +
+					"}",
+				},
+		     "\"" + OUTPUT_DIR +  File.separator + "X.java\""
+		     + " --release 6 -d \"" + OUTPUT_DIR + "\"",
+		     "",
+		     "",
+		     true);
+		String expectedOutput = "// Compiled from X.java (version 1.6 : 50.0, super bit)";
+			checkDisassembledClassFile(OUTPUT_DIR + File.separator + "X.class", "X", expectedOutput);
+	}
+	public void testReleaseOption4() throws Exception {
+		this.runNegativeTest(
+				new String[] {
+					"X.java",
+					"/** */\n" +
+					"public class X {\n" +
+					"}",
+				},
+		     "\"" + OUTPUT_DIR +  File.separator + "X.java\""
+		     + " --release 6 -source 1.6 -d \"" + OUTPUT_DIR + "\"",
+		     "",
+		     "option -source is not supported when --release is used\n",
+		     true);
+	}
+	public void testReleaseOption5() throws Exception {
+		this.runNegativeTest(
+				new String[] {
+					"X.java",
+					"/** */\n" +
+					"public class X {\n" +
+					"}",
+				},
+		     "\"" + OUTPUT_DIR +  File.separator + "X.java\""
+		     + " --release 8 -target 1.8 -d \"" + OUTPUT_DIR + "\"",
+		     "",
+		     "option -target is not supported when --release is used\n",
+		     true);
+	}
+	public void testReleaseOption6() throws Exception {
+		this.runNegativeTest(
+				new String[] {
+					"X.java",
+					"/** */\n" +
+					"public class X {\n" +
+					"}",
+				},
+		     "\"" + OUTPUT_DIR +  File.separator + "X.java\""
+		     + " --release 5 -d \"" + OUTPUT_DIR + "\"",
+		     "",
+		     "release version 5 is not supported\n",
+		     true);
+	}
+	public void testReleaseOption7() throws Exception {
+		this.runConformTest(
+				new String[] {
+					"X.java",
+					"import java.util.stream.*;\n" +
+					"/** */\n" +
+					"public class X {\n" +
+					"	public Stream<String> emptyStream() {\n" +
+					"		Stream<String> st = Stream.empty();\n" +
+					"		return st;\n" +
+					"	}\n" +
+					"}",
+				},
+		     "\"" + OUTPUT_DIR +  File.separator + "X.java\""
+		     + " --release 8 -d \"" + OUTPUT_DIR + "\"",
+		     "",
+		     "",
+		     true);
+	}
+	public void testReleaseOption8() throws Exception {
+		this.runNegativeTest(
+				new String[] {
+					"X.java",
+					"/** */\n" +
+					"public class X {\n" +
+					"	public java.util.stream.Stream<String> emptyStream() {\n" +
+					"		return null;\n" +
+					"	}\n" +
+					"}",
+				},
+		     "\"" + OUTPUT_DIR +  File.separator + "X.java\""
+		     + " --release 7 -d \"" + OUTPUT_DIR + "\"",
+		     "",
+		     "----------\n" + 
+    		 "1. ERROR in ---OUTPUT_DIR_PLACEHOLDER---/X.java (at line 3)\n" + 
+    		 "	public java.util.stream.Stream<String> emptyStream() {\n" + 
+    		 "	       ^^^^^^^^^^^^^^^^\n" + 
+    		 "java.util.stream cannot be resolved to a type\n" + 
+    		 "----------\n" + 
+    		 "1 problem (1 error)\n",
+		     true);
+	}
+	public void testReleaseOption9() throws Exception {
+		this.runNegativeTest(
+				new String[] {
+					"X.java",
+					"interface I {\n" +
+					"  int add(int x, int y);\n" +
+					"}\n" +
+					"public class X {\n" +
+					"  public static void main(String[] args) {\n" +
+					"    I i = (x, y) -> {\n" +
+					"      return x + y;\n" +
+					"    };\n" +
+					"  }\n" +
+					"}\n",
+				},
+		     "\"" + OUTPUT_DIR +  File.separator + "X.java\""
+		     + " --release 7 -d \"" + OUTPUT_DIR + "\"",
+		     "",
+		     "----------\n" + 
+    		 "1. ERROR in ---OUTPUT_DIR_PLACEHOLDER---/X.java (at line 6)\n" + 
+    		 "	I i = (x, y) -> {\n" + 
+    		 "	      ^^^^^^^^^\n" + 
+    		 "Lambda expressions are allowed only at source level 1.8 or above\n" + 
+    		 "----------\n" + 
+    		 "1 problem (1 error)\n",
+		     true);
+	}
+	public void testReleaseOption10() throws Exception {
+		this.runNegativeTest(
+				new String[] {
+					"X.java",
+					"import java.io.*;\n" + 
+					"\n" + 
+					"public class X {\n" + 
+					"	public static void main(String[] args) {\n" + 
+					"		try {\n" + 
+					"			System.out.println();\n" + 
+					"			Reader r = new FileReader(args[0]);\n" + 
+					"			r.read();\n" + 
+					"		} catch(IOException | FileNotFoundException e) {\n" +
+					"			e.printStackTrace();\n" + 
+					"		}\n" + 
+					"	}\n" + 
+					"}",
+				},
+		     "\"" + OUTPUT_DIR +  File.separator + "X.java\""
+		     + " --release 6 -d \"" + OUTPUT_DIR + "\"",
+		     "",
+		     "----------\n" + 
+    		 "1. ERROR in ---OUTPUT_DIR_PLACEHOLDER---/X.java (at line 9)\n" + 
+    		 "	} catch(IOException | FileNotFoundException e) {\n" + 
+    		 "	        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n" + 
+    		 "Multi-catch parameters are not allowed for source level below 1.7\n" + 
+    		 "----------\n" + 
+    		 "2. ERROR in ---OUTPUT_DIR_PLACEHOLDER---/X.java (at line 9)\n" + 
+    		 "	} catch(IOException | FileNotFoundException e) {\n" + 
+    		 "	                      ^^^^^^^^^^^^^^^^^^^^^\n" + 
+    		 "The exception FileNotFoundException is already caught by the alternative IOException\n" + 
+    		 "----------\n" + 
+    		 "2 problems (2 errors)\n",
+		     true);
+	}
+	public void testReleaseOption11() throws Exception {
+		this.runNegativeTest(
+				new String[] {
+					"X.java",
+					"public class X {\n" + 
+					"	public static void main(String[] args) {\n" + 
+					"	}\n" + 
+					"}",
+				},
+		     "\"" + OUTPUT_DIR +  File.separator + "X.java\"" +
+		     " -bootclasspath " + OUTPUT_DIR + File.separator + "src " +
+		     " --release 9 -d \"" + OUTPUT_DIR + "\"",
+		     "",
+    		 "option -bootclasspath not supported at compliance level 9 and above\n",
+		     true);
+	}
+	public void _testReleaseOption12() throws Exception {
+		String javaHome = System.getProperty("java.home");
+		this.runNegativeTest(
+				new String[] {
+					"X.java",
+					"public class X {\n" + 
+					"	public static void main(String[] args) {\n" + 
+					"	}\n" + 
+					"}",
+				},
+		     "\"" + OUTPUT_DIR +  File.separator + "X.java\"" +
+		      " --system \"" + javaHome + "\"" +
+		     " --release 6 -d \"" + OUTPUT_DIR + "\"",
+		     "",
+		     "----------\n" + 
+    		 "option --system not supported below compliance level 9",
+		     true);
+	}
+	public void testReleaseOption13() {
+		runConformModuleTest(
+			new String[] {
+				"p/X.java",
+				"package p;\n" +
+				"public class X {\n" +
+				"	public static void main(String[] args) {\n" +
+				"	}\n" +
+				"}",
+				"module-info.java",
+				"module mod.one { \n" +
+				"	requires java.base;\n" +
+				"}"
+	        },
+			" --release 9 \"" + OUTPUT_DIR +  File.separator + "module-info.java\" "
+	        + "\"" + OUTPUT_DIR +  File.separator + "p/X.java\"",
+	        "",
+	        "",
+	        true);
+	}
+	public void testReleaseOption14() {
+		runNegativeModuleTest(
+			new String[] {
+				"module-info.java",
+				"module mod.one { \n" +
+				"}"
+	        },
+			" --release 8 \"" + OUTPUT_DIR +  File.separator + "module-info.java\" ",
+	        "",
+	        "----------\n" + 
+    		"1. ERROR in ---OUTPUT_DIR_PLACEHOLDER---/module-info.java (at line 1)\n" + 
+    		"	module mod.one { \n" + 
+    		"	^^^^^^\n" + 
+    		"Syntax error on token \"module\", package expected\n" + 
+    		"----------\n" + 
+    		"2. ERROR in ---OUTPUT_DIR_PLACEHOLDER---/module-info.java (at line 1)\n" + 
+    		"	module mod.one { \n" + 
+    		"}\n" + 
+    		"	               ^^^^\n" + 
+    		"Syntax error on tokens, delete these tokens\n" + 
+    		"----------\n" + 
+    		"2 problems (2 errors)\n",
+	        true,
+	        /*not tested with javac*/"");
+	}
+	// Test from https://bugs.eclipse.org/bugs/show_bug.cgi?id=526997
+	public void testReleaseOption15() {
+		runConformModuleTest(
+			new String[] {
+				"foo/Module.java",
+				"package foo;\n" +
+				"public class Module {}\n",
+				"foo/X.java",
+				"package foo;\n" +
+				"public class X { \n" +
+				"	public Module getModule(String name) {\n" + 
+				"		return null;\n" +
+				"	}\n" + 
+				"}"
+	        },
+			" --release 8 \"" + OUTPUT_DIR +  File.separator + "foo" + File.separator + "Module.java\" " +
+			"\"" +  OUTPUT_DIR +  File.separator + "foo" + File.separator + "X.java\" ",
+	        "",
+    		"",
+	        true,
+	        /*not tested with javac*/"");
+	}
+	// Test from https://bugs.eclipse.org/bugs/show_bug.cgi?id=526997
+	public void testReleaseOption16() {
+		runNegativeModuleTest(
+			new String[] {
+				"foo/Module.java",
+				"package foo;\n" +
+				"public class Module {}\n",
+				"bar/X.java",
+				"package bar;\n" +
+				"import foo.*;\n" +
+				"public class X { \n" +
+				"	public Module getModule(String name) {\n" + 
+				"		return null;\n" +
+				"	}\n" + 
+				"}"
+	        },
+			" -source 9 \"" + OUTPUT_DIR +  File.separator + "foo" + File.separator + "Module.java\" " +
+			"\"" +  OUTPUT_DIR +  File.separator + "bar" + File.separator + "X.java\" ",
+	        "",
+	        "----------\n" + 
+    		"1. ERROR in ---OUTPUT_DIR_PLACEHOLDER---/bar/X.java (at line 4)\n" + 
+    		"	public Module getModule(String name) {\n" + 
+    		"	       ^^^^^^\n" + 
+    		"The type Module is ambiguous\n" + 
+    		"----------\n" + 
+    		"1 problem (1 error)\n",
+	        true,
+	        /*not tested with javac*/"");
+	}
+	public void testReleaseOption17() {
+		runNegativeModuleTest(
+			new String[] {
+				"foo/Module.java",
+				"package foo;\n" +
+				"public class Module {}\n",
+				"foo/X.java",
+				"package foo;\n" +
+				"public class X { \n" +
+				"	public Module getModule(String name) {\n" + 
+				"		return null;\n" +
+				"	}\n" + 
+				"}"
+	        },
+			" --release 60 \"" + OUTPUT_DIR +  File.separator + "foo" + File.separator + "Module.java\" " +
+			"\"" +  OUTPUT_DIR +  File.separator + "foo" + File.separator + "X.java\" ",
+	        "",
+    		"release 60 is not found in the system\n",
+	        true,
+	        /*not tested with javac*/"");
+	}
+	public void testReleaseOption18() {
+		runNegativeModuleTest(
+			new String[] {
+				"X.java",
+				"/** */\n" +
+				"public class X {\n" +
+				"}",
+				},
+			" --release 6 -1.8 \"" + OUTPUT_DIR +  File.separator + "foo" + File.separator + "Module.java\" " +
+			"\"" +  OUTPUT_DIR +  File.separator + "foo" + File.separator + "X.java\" ",
+	        "",
+    		"option 1.8 is not supported when --release is used\n",
+	        true,
+	        /*not tested with javac*/"");
+	}
+	public void testReleaseOption19() {
+		runNegativeModuleTest(
+			new String[] {
+			"X.java",
+			"/** */\n" +
+			"public class X {\n" +
+			"}",
+			},
+			" -9 --release 9 \"" + OUTPUT_DIR +  File.separator + "foo" + File.separator + "Module.java\" " +
+			"\"" +  OUTPUT_DIR +  File.separator + "foo" + File.separator + "X.java\" ",
+	        "",
+    		"option 9 is not supported when --release is used\n",
+	        true,
+	        /*not tested with javac*/"");
+	}
+	public void testLimitModules1() {
+		File outputDirectory = new File(OUTPUT_DIR);
+		Util.flushDirectoryContent(outputDirectory);
+		String out = "bin";
+		String directory = OUTPUT_DIR + File.separator + "src";
+
+		String moduleLoc = directory + File.separator + "mod.x";
+		List<String> files = new ArrayList<>(); 
+		writeFileCollecting(files, moduleLoc, "module-info.java", 
+						"module mod.x { \n" +
+						"	exports pm;\n" +
+						"	requires java.sql;\n" +
+						"}");
+		writeFileCollecting(files, moduleLoc + File.separator + "pm", "C1.java", 
+						"package pm;\n" +
+						"public class C1 {\n" +
+						"}\n");
+
+
+		StringBuffer buffer = new StringBuffer();
+		buffer.append("-d " + OUTPUT_DIR + File.separator + out )
+			.append(" -9 ")
+			.append(" -classpath \"")
+			.append(Util.getJavaClassLibsAsString())
+			.append("\" ")
+			.append(" --limit-modules java.base")
+			.append(" --module-source-path " + "\"" + directory + "\"");
+		runNegativeModuleTest(files, buffer,
+				"",
+				"----------\n" + 
+				"1. ERROR in ---OUTPUT_DIR_PLACEHOLDER---/src/mod.x/module-info.java (at line 3)\n" + 
+				"	requires java.sql;\n" + 
+				"	         ^^^^^^^^\n" + 
+				"java.sql cannot be resolved to a module\n" + 
+				"----------\n" + 
+				"1 problem (1 error)\n",
+				false,
+				"is not accessible to clients");
+	}
+	public void testLimitModules2() {
+		File outputDirectory = new File(OUTPUT_DIR);
+		Util.flushDirectoryContent(outputDirectory);
+		String out = "bin";
+		String directory = OUTPUT_DIR + File.separator + "src";
+
+		String moduleLoc = directory + File.separator + "mod.x";
+		List<String> files = new ArrayList<>(); 
+		writeFileCollecting(files, moduleLoc, "module-info.java", 
+						"module mod.x { \n" +
+						"	exports pm;\n" +
+						"}");
+		writeFileCollecting(files, moduleLoc + File.separator + "pm", "C1.java", 
+						"package pm;\n" +
+						"import java.sql.Connection;\n" + 
+						"public class C1 {\n" +
+						"}\n");
+
+
+		StringBuffer buffer = new StringBuffer();
+		buffer.append("-d " + OUTPUT_DIR + File.separator + out )
+			.append(" -9 ")
+			.append(" -classpath \"")
+			.append(Util.getJavaClassLibsAsString())
+			.append("\" ")
+			.append(" --limit-modules java.base")
+			.append(" --module-source-path " + "\"" + directory + "\"");
+		runNegativeModuleTest(files, buffer,
+				"",
+				"----------\n" + 
+				"1. ERROR in ---OUTPUT_DIR_PLACEHOLDER---/src/mod.x/pm/C1.java (at line 2)\n" + 
+				"	import java.sql.Connection;\n" + 
+				"	       ^^^^^^^^\n" + 
+				"The import java.sql cannot be resolved\n" + 
+				"----------\n" + 
+				"1 problem (1 error)\n",
+				false,
+				"is not accessible to clients");
+	}
+	public void testLimitModules3() {
+		File outputDirectory = new File(OUTPUT_DIR);
+		Util.flushDirectoryContent(outputDirectory);
+		String out = "bin";
+		String directory = OUTPUT_DIR + File.separator + "src";
+
+		String moduleLoc = directory + File.separator + "mod.x";
+		List<String> files = new ArrayList<>(); 
+		writeFileCollecting(files, moduleLoc, "module-info.java", 
+						"module mod.x { \n" +
+						"	exports pm;\n" +
+						"}");
+		writeFileCollecting(files, moduleLoc + File.separator + "pm", "C1.java", 
+						"package pm;\n" +
+						"public class C1 {\n" +
+						"}\n");
+
+
+		StringBuffer buffer = new StringBuffer();
+		buffer.append("-d " + OUTPUT_DIR + File.separator + out )
+			.append(" -9 ")
+			.append(" -classpath \"")
+			.append(Util.getJavaClassLibsAsString())
+			.append("\" ")
+			.append(" --limit-modules java.sql")
+			.append(" --module-source-path " + "\"" + directory + "\"");
+		runConformModuleTest(files, buffer,
+				"",
+				"",
+				false);
+	}
+	public void testLimitModules4() {
+		Util.flushDirectoryContent(new File(OUTPUT_DIR));
+		String outDir = OUTPUT_DIR + File.separator + "bin";
+		String srcDir = OUTPUT_DIR + File.separator + "src";
+		File modDir = new File(OUTPUT_DIR + File.separator + "mod");
+		createReusableModules(srcDir, outDir, modDir);
+		String moduleLoc = srcDir + File.separator + "mod.three";
+		List<String> files = new ArrayList<>(); 
+		writeFileCollecting(files, moduleLoc, "module-info.java", 
+						"module mod.three { \n" +
+						"	requires mod.one;\n" +
+						"	requires mod.two;\n" +
+						"}");
+
+
+		StringBuffer buffer = new StringBuffer();
+		buffer.append("-d " + outDir )
+			.append(" -9 ")
+			.append(" --module-path \"")
+			.append(Util.getJavaClassLibsAsString())
+			.append(modDir.getAbsolutePath())
+			.append("\" ")
+			.append(" --limit-modules mod.one,mod.two ")
+			.append(" --module-source-path " + "\"" + srcDir + "\" ");
+		runConformModuleTest(files, buffer,
+				"",
+				"",
+				false);
+	}
+	public void testLimitModules5() {
+		Util.flushDirectoryContent(new File(OUTPUT_DIR));
+		String outDir = OUTPUT_DIR + File.separator + "bin";
+		String srcDir = OUTPUT_DIR + File.separator + "src";
+		File modDir = new File(OUTPUT_DIR + File.separator + "mod");
+		createReusableModules(srcDir, outDir, modDir);
+		String moduleLoc = srcDir + File.separator + "mod.three";
+		List<String> files = new ArrayList<>(); 
+		writeFileCollecting(files, moduleLoc, "module-info.java", 
+						"module mod.three { \n" +
+						"	requires mod.one;\n" +
+						"	requires mod.two;\n" +
+						"}");
+
+
+		StringBuffer buffer = new StringBuffer();
+		buffer.append("-d " + outDir )
+			.append(" -9 ")
+			.append(" --module-path \"")
+			.append(Util.getJavaClassLibsAsString())
+			.append(modDir.getAbsolutePath())
+			.append("\" ")
+			.append(" --limit-modules mod.one ")
+			.append(" --module-source-path " + "\"" + srcDir + "\" ");
+		runNegativeModuleTest(files, buffer,
+				"",
+				"----------\n" + 
+				"1. ERROR in ---OUTPUT_DIR_PLACEHOLDER---/src/mod.three/module-info.java (at line 3)\n" + 
+				"	requires mod.two;\n" + 
+				"	         ^^^^^^^\n" + 
+				"mod.two cannot be resolved to a module\n" + 
+				"----------\n" + 
+				"1 problem (1 error)\n",
+				false,
+				"");
+	}
 }
diff --git a/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/ClasspathJep247.java b/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/ClasspathJep247.java
new file mode 100644
index 0000000..69f9a6a
--- /dev/null
+++ b/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/ClasspathJep247.java
@@ -0,0 +1,215 @@
+package org.eclipse.jdt.internal.compiler.batch;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.nio.file.DirectoryStream;
+import java.nio.file.FileSystemNotFoundException;
+import java.nio.file.FileSystems;
+import java.nio.file.FileVisitResult;
+import java.nio.file.FileVisitor;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.jdt.core.compiler.CharOperation;
+import org.eclipse.jdt.internal.compiler.batch.FileSystem.Classpath;
+import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader;
+import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException;
+import org.eclipse.jdt.internal.compiler.env.AccessRuleSet;
+import org.eclipse.jdt.internal.compiler.env.IModule;
+import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer;
+import org.eclipse.jdt.internal.compiler.util.Util;
+
+public class ClasspathJep247 extends ClasspathLocation {
+
+	private java.nio.file.FileSystem fs = null;
+	private String release = null;
+	private String[] subReleases = null;
+	private Path releasePath = null;
+	private File file = null;
+	private Set<String> packageCache;
+	
+	public ClasspathJep247(File jdkHome, String release, AccessRuleSet accessRuleSet) {
+		super(accessRuleSet, null);
+		this.release = release;
+		this.file = jdkHome;
+	}
+	public List<Classpath> fetchLinkedJars(FileSystem.ClasspathSectionProblemReporter problemReporter) {
+		 return null;
+	}
+	public NameEnvironmentAnswer findClass(char[] typeName, String qualifiedPackageName, String moduleName, String qualifiedBinaryFileName) {
+		return findClass(typeName, qualifiedPackageName, moduleName, qualifiedBinaryFileName, false);
+	}
+	public NameEnvironmentAnswer findClass(char[] typeName, String qualifiedPackageName, String moduleName, String qualifiedBinaryFileName, boolean asBinaryOnly) {
+		if (!isPackage(qualifiedPackageName, moduleName))
+			return null; // most common case
+
+		try {
+			//TODO: Check if any conversion needed for path separator
+			ClassFileReader reader = null;
+			byte[] content = null;
+			qualifiedBinaryFileName = qualifiedBinaryFileName.replace(".class", ".sig"); //$NON-NLS-1$ //$NON-NLS-2$
+			if (this.subReleases != null && this.subReleases.length > 0) {
+				for (String rel : this.subReleases) {
+					Path p = this.fs.getPath(rel, qualifiedBinaryFileName);
+					if (Files.exists(p)) {
+						content = Files.readAllBytes(p);
+						if (content != null) 
+							break;
+					}
+				}
+			} else {
+				content = Files.readAllBytes(this.fs.getPath(this.release, qualifiedBinaryFileName));
+			}
+			if (content != null) {
+				reader = new ClassFileReader(content, qualifiedBinaryFileName.toCharArray());
+				return new NameEnvironmentAnswer(reader, fetchAccessRestriction(qualifiedBinaryFileName), null);
+			}
+		} catch(ClassFormatException e) {
+			// Continue
+		} catch (IOException e) {
+			// continue
+		}
+		return null;
+	}
+	@Override
+	public boolean hasAnnotationFileFor(String qualifiedTypeName) {
+		return false;
+	}
+	public char[][][] findTypeNames(final String qualifiedPackageName, String moduleName) {
+		// TODO: Revisit
+		return null;
+	}
+
+	public void initialize() throws IOException {
+		if (this.release == null) {
+			return;
+		}
+		Path filePath = this.file.toPath().resolve("lib").resolve("ct.sym"); //$NON-NLS-1$ //$NON-NLS-2$
+		URI t = filePath.toUri();
+		if (!Files.exists(filePath)) {
+			return;
+		}
+		URI uri = URI.create("jar:file:" + t.getPath()); //$NON-NLS-1$
+		try {
+			this.fs = FileSystems.getFileSystem(uri);
+		} catch(FileSystemNotFoundException fne) {
+			// Ignore and move on
+		}
+		if (this.fs == null) {
+			HashMap<String, ?> env = new HashMap<>();
+			this.fs = FileSystems.newFileSystem(uri, env);
+		}
+		this.releasePath = this.fs.getPath(""); //$NON-NLS-1$
+		if (!Files.exists(this.fs.getPath(this.release))) {
+			throw new IllegalArgumentException("release " + this.release + " is not found in the system");  //$NON-NLS-1$//$NON-NLS-2$
+		}
+	}
+	void acceptModule(ClassFileReader reader) {
+		// Nothing to do
+	}
+	protected void addToPackageCache(String packageName, boolean endsWithSep) {
+		if (this.packageCache.contains(packageName))
+			return;
+		this.packageCache.add(packageName);
+	}
+	public synchronized char[][] getModulesDeclaringPackage(String qualifiedPackageName, String moduleName) {
+		// Ignore moduleName as this has nothing to do with modules (as of now)
+		if (this.packageCache != null)
+			return singletonModuleNameIf(this.packageCache.contains(qualifiedPackageName));
+
+		this.packageCache = new HashSet<>(41);
+		this.packageCache.add(Util.EMPTY_STRING);
+		List<String> sub = new ArrayList<>();
+		try (DirectoryStream<java.nio.file.Path> stream = Files.newDirectoryStream(this.releasePath)) {
+			for (final java.nio.file.Path subdir: stream) {
+				String rel = subdir.getFileName().toString();
+				if (rel.contains(this.release)) {
+					sub.add(rel);
+				} else {
+					continue;
+				}
+				Files.walkFileTree(subdir, new FileVisitor<java.nio.file.Path>() {
+					@Override
+					public FileVisitResult preVisitDirectory(java.nio.file.Path dir, BasicFileAttributes attrs) throws IOException {
+						if (dir.getNameCount() <= 1)
+							return FileVisitResult.CONTINUE;
+						Path relative = dir.subpath(1, dir.getNameCount());
+						addToPackageCache(relative.toString(), false);
+						return FileVisitResult.CONTINUE;
+					}
+
+					@Override
+					public FileVisitResult visitFile(java.nio.file.Path f, BasicFileAttributes attrs) throws IOException {
+						return FileVisitResult.CONTINUE;
+					}
+
+					@Override
+					public FileVisitResult visitFileFailed(java.nio.file.Path f, IOException exc) throws IOException {
+						return FileVisitResult.CONTINUE;
+					}
+
+					@Override
+					public FileVisitResult postVisitDirectory(java.nio.file.Path dir, IOException exc) throws IOException {
+						return FileVisitResult.CONTINUE;
+					}
+				});
+			}
+		} catch (IOException e) {
+			e.printStackTrace();
+			// Rethrow
+		}
+		this.subReleases = sub.toArray(new String[sub.size()]);
+		return singletonModuleNameIf(this.packageCache.contains(qualifiedPackageName));
+	}
+	@Override
+	public boolean hasCompilationUnit(String qualifiedPackageName, String moduleName) {
+		// TOOD: Revisit
+		return false;
+	}
+	public void reset() {
+		try {
+			this.fs.close();
+		} catch (IOException e) {
+			// Move on
+		}
+	}
+	public String toString() {
+		return "Classpath for JEP 247 for JDK " + this.file.getPath(); //$NON-NLS-1$
+	}
+	public char[] normalizedPath() {
+		if (this.normalizedPath == null) {
+			String path2 = this.getPath();
+			char[] rawName = path2.toCharArray();
+			if (File.separatorChar == '\\') {
+				CharOperation.replace(rawName, '\\', '/');
+			}
+			this.normalizedPath = CharOperation.subarray(rawName, 0, CharOperation.lastIndexOf('.', rawName));
+		}
+		return this.normalizedPath;
+	}
+	public String getPath() {
+		if (this.path == null) {
+			try {
+				this.path = this.file.getCanonicalPath();
+			} catch (IOException e) {
+				// in case of error, simply return the absolute path
+				this.path = this.file.getAbsolutePath();
+			}
+		}
+		return this.path;
+	}
+	public int getMode() {
+		return BINARY;
+	}
+
+	public IModule getModule() {
+		return null;
+	}
+}
diff --git a/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/ClasspathJrt.java b/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/ClasspathJrt.java
index be2e520..d8830a1 100644
--- a/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/ClasspathJrt.java
+++ b/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/ClasspathJrt.java
@@ -21,7 +21,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.stream.Collectors;
+import java.util.function.Function;
 import java.util.zip.ZipFile;
 
 import org.eclipse.jdt.core.compiler.CharOperation;
@@ -33,6 +33,7 @@
 import org.eclipse.jdt.internal.compiler.env.IModule;
 import org.eclipse.jdt.internal.compiler.env.IMultiModuleEntry;
 import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer;
+import org.eclipse.jdt.internal.compiler.env.IModule.IPackageExport;
 import org.eclipse.jdt.internal.compiler.lookup.BinaryTypeBinding.ExternalAnnotationStatus;
 import org.eclipse.jdt.internal.compiler.util.JRTUtil;
 import org.eclipse.jdt.internal.compiler.util.SuffixConstants;
@@ -250,11 +251,41 @@
 	}
 	
 	@Override
-	public Collection<String> getModuleNames(Collection<String> limitModule) {
-		return ModulesCache.values().stream()
-				.flatMap(entryMap -> entryMap.keySet().stream())
-				.filter(m -> limitModule == null || limitModule.contains(m)) // TODO: implement algo from JEP 261 (root selection & transitive closure)
-				.collect(Collectors.toList());
+	public Collection<String> getModuleNames(Collection<String> limitModule, Function<String, IModule> getModule) {
+		Map<String, IModule> cache = ModulesCache.get(this.file.getPath());
+		return selectModules(cache.keySet(), limitModule, getModule);
+	}
+	protected <T> List<String> allModules(Iterable<T> allSystemModules, Function<T,String> getModuleName, Function<T,IModule> getModule) {
+		List<String> result = new ArrayList<>();
+		boolean hasJavaDotSE = false;
+		for (T mod : allSystemModules) {
+			String moduleName = getModuleName.apply(mod);
+			if ("java.se".equals(moduleName)) { //$NON-NLS-1$
+				result.add(moduleName);
+				hasJavaDotSE = true;
+				break;
+			}
+		}
+		for (T mod : allSystemModules) {
+			String moduleName = getModuleName.apply(mod);
+			boolean isJavaDotStart = moduleName.startsWith("java."); //$NON-NLS-1$
+			boolean isPotentialRoot = !isJavaDotStart;	// always include non-java.*
+			if (!hasJavaDotSE)
+				isPotentialRoot |= isJavaDotStart;		// no java.se => add all java.*
+			
+			if (isPotentialRoot) {
+				IModule m = getModule.apply(mod);
+				if (m != null) {
+					for (IPackageExport packageExport : m.exports()) {
+						if (!packageExport.isQualified()) {
+							result.add(moduleName);
+							break;
+						}
+					}
+				}
+			}
+		}
+		return result;
 	}
 //	protected void addToPackageCache(String fileName, boolean endsWithSep) {
 //		int last = endsWithSep ? fileName.length() : fileName.lastIndexOf('/');
diff --git a/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/ClasspathLocation.java b/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/ClasspathLocation.java
index 4b2d508..2426b61 100644
--- a/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/ClasspathLocation.java
+++ b/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/ClasspathLocation.java
@@ -11,13 +11,19 @@
 package org.eclipse.jdt.internal.compiler.batch;
 
 import java.io.File;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Function;
 
 import org.eclipse.jdt.core.compiler.CharOperation;
 import org.eclipse.jdt.internal.compiler.env.AccessRestriction;
 import org.eclipse.jdt.internal.compiler.env.AccessRuleSet;
 import org.eclipse.jdt.internal.compiler.env.IModule;
+import org.eclipse.jdt.internal.compiler.env.IModule.IModuleReference;
 import org.eclipse.jdt.internal.compiler.lookup.ModuleBinding;
 import org.eclipse.jdt.internal.compiler.util.SuffixConstants;
 
@@ -120,10 +126,52 @@
 	}
 	@Override
 	public Collection<String> getModuleNames(Collection<String> limitModules) {
-		if (this.module != null)
-			return Collections.singletonList(String.valueOf(this.module.name()));
+		return getModuleNames(limitModules, m -> getModule(m.toCharArray()));
+	}
+	@Override
+	public Collection<String> getModuleNames(Collection<String> limitModules, Function<String,IModule> getModule) {
+		if (this.module != null) {
+			String name = String.valueOf(this.module.name());
+			return selectModules(Collections.singleton(name), limitModules, getModule);
+		}
 		return Collections.emptyList();
 	}
+	protected Collection<String> selectModules(Set<String> modules, Collection<String> limitModules, Function<String,IModule> getModule) {
+		Collection<String> rootModules;
+		if (limitModules != null) {
+			Set<String> result = new HashSet<>(modules);
+			result.retainAll(limitModules);
+			rootModules = result;
+		} else {
+			rootModules = allModules(modules, s -> s, m -> getModule(m.toCharArray()));
+		}
+		Set<String> allModules = new HashSet<>(rootModules);
+		for (String mod : rootModules)
+			addRequired(mod, allModules, getModule);
+		return allModules;
+	}
+
+	private void addRequired(String mod, Set<String> allModules, Function<String,IModule> getModule) {
+		IModule iMod = getModule(mod.toCharArray());
+		if (iMod != null) {
+			for (IModuleReference requiredRef : iMod.requires()) {
+				IModule reqMod = getModule.apply(new String(requiredRef.name()));
+				if (reqMod != null) {
+					String reqModName = String.valueOf(reqMod.name());
+					if (allModules.add(reqModName))
+						addRequired(reqModName, allModules, getModule);
+				}
+			}
+		}
+	}
+	protected <T> List<String> allModules(Iterable<T> allSystemModules, Function<T,String> getModuleName, Function<T,IModule> getModule) {
+		List<String> result = new ArrayList<>();
+		for (T mod : allSystemModules) {
+			String moduleName = getModuleName.apply(mod);
+			result.add(moduleName);
+		}
+		return result;
+	}
 
 	public boolean isPackage(String qualifiedPackageName, String moduleName) {
 		return getModulesDeclaringPackage(qualifiedPackageName, moduleName) != null;
diff --git a/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/FileSystem.java b/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/FileSystem.java
index b8fc121..e31eca6 100644
--- a/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/FileSystem.java
+++ b/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/FileSystem.java
@@ -14,6 +14,7 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.nio.file.InvalidPathException;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
@@ -119,6 +120,7 @@
 		public void acceptModule(IModule module);
 		public String getDestinationPath();
 		Collection<String> getModuleNames(Collection<String> limitModules);
+		Collection<String> getModuleNames(Collection<String> limitModules, Function<String,IModule> getModule);
 	}
 	public interface ClasspathSectionProblemReporter {
 		void invalidClasspathSection(String jarFilePath);
@@ -177,7 +179,7 @@
 		Classpath classpath = getClasspath(classpathNames[i], encoding, null, null);
 		try {
 			classpath.initialize();
-			for (String moduleName : classpath.getModuleNames(null)) // TODO limit-modules?
+			for (String moduleName : classpath.getModuleNames(null))
 				this.moduleLocations.put(moduleName, classpath);
 			this.classpaths[counter++] = classpath;
 		} catch (IOException e) {
@@ -189,7 +191,7 @@
 	}
 	initializeKnownFileNames(initialFileNames);
 }
-protected FileSystem(Classpath[] paths, String[] initialFileNames, boolean annotationsFromClasspath) {
+protected FileSystem(Classpath[] paths, String[] initialFileNames, boolean annotationsFromClasspath, Set<String> limitedModules) {
 	final int length = paths.length;
 	int counter = 0;
 	this.classpaths = new FileSystem.Classpath[length];
@@ -197,10 +199,8 @@
 		final Classpath classpath = paths[i];
 		try {
 			classpath.initialize();
-			for (String moduleName : classpath.getModuleNames(null)) // TODO limit-modules?
-				this.moduleLocations.put(moduleName, classpath);
 			this.classpaths[counter++] = classpath;
-		} catch(IOException | IllegalArgumentException exception) {
+		} catch(IOException | InvalidPathException exception) {
 			// JRE 9 could throw an IAE if the linked JAR paths have invalid chars, such as ":"
 			// ignore
 		}
@@ -209,9 +209,30 @@
 		// should not happen
 		System.arraycopy(this.classpaths, 0, (this.classpaths = new FileSystem.Classpath[counter]), 0, counter);
 	}
+	initializeModuleLocations(limitedModules);
 	initializeKnownFileNames(initialFileNames);
 	this.annotationsFromClasspath = annotationsFromClasspath;
 }
+private void initializeModuleLocations(Set<String> limitedModules) {
+	// First create the mapping of all module/Classpath
+	// since the second iteration of getModuleNames() can't be relied on for 
+	// to get the right origin of module
+	Map<String, Classpath> moduleMap = new HashMap<>();
+	for (Classpath c : this.classpaths) {
+		for (String moduleName : c.getModuleNames(null)) {
+			moduleMap.put(moduleName, c);
+		}
+	}
+	for (Classpath c : this.classpaths) {
+		for (String moduleName : c.getModuleNames(limitedModules, m -> getModuleFromEnvironment(m.toCharArray()))) {
+			Classpath classpath = moduleMap.get(moduleName);
+			this.moduleLocations.put(moduleName, classpath);
+		}
+	}
+}
+protected FileSystem(Classpath[] paths, String[] initialFileNames, boolean annotationsFromClasspath) {
+	this(paths, initialFileNames, annotationsFromClasspath, null);
+}
 public static Classpath getClasspath(String classpathName, String encoding, AccessRuleSet accessRuleSet) {
 	return getClasspath(classpathName, encoding, false, accessRuleSet, null, null);
 }
@@ -221,6 +242,9 @@
 public static Classpath getJrtClasspath(String jdkHome, String encoding, AccessRuleSet accessRuleSet, Map<String, String> options) {
 	return new ClasspathJrt(new File(convertPathSeparators(jdkHome)), true, accessRuleSet, null);
 }
+public static Classpath getOlderSystemRelease(String jdkHome, String release, AccessRuleSet accessRuleSet) {
+	return new ClasspathJep247(new File(convertPathSeparators(jdkHome)), release, accessRuleSet);
+}
 public static Classpath getClasspath(String classpathName, String encoding,
 		boolean isSourceOnly, AccessRuleSet accessRuleSet,
 		String destinationPath, Map<String, String> options) {
@@ -587,6 +611,20 @@
 	if (this.module != null && CharOperation.equals(name, this.module.name())) {
 		return this.module;
 	}
+	if (this.moduleLocations.containsKey(new String(name))) {
+		for (Classpath classpath : this.classpaths) {
+			IModule mod = classpath.getModule(name);
+			if (mod != null) {
+				return mod;
+			}
+		}
+	}
+	return null;
+}
+public IModule getModuleFromEnvironment(char[] name) {
+	if (this.module != null && CharOperation.equals(name, this.module.name())) {
+		return this.module;
+	}
 	for (Classpath classpath : this.classpaths) {
 		IModule mod = classpath.getModule(name);
 		if (mod != null) {
diff --git a/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/Main.java b/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/Main.java
index a3155cf..c363bc7 100644
--- a/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/Main.java
+++ b/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/Main.java
@@ -1364,6 +1364,7 @@
 	private List<String> addonExports = Collections.EMPTY_LIST;
 	private List<String> addonReads = Collections.EMPTY_LIST;
 	public Set<String> rootModules = Collections.EMPTY_SET;
+	public Set<String> limitedModules;
 
 	public Locale compilerLocale;
 	public CompilerOptions compilerOptions; // read-only
@@ -1378,6 +1379,7 @@
 	// == Main.NONE: absorbent element, do not output class files;
 	// else: use as the path of the directory into which class files must
 	//       be written.
+	protected String releaseVersion;
 	private boolean didSpecifySource;
 	private boolean didSpecifyTarget;
 	public String[] encodings;
@@ -1403,7 +1405,7 @@
 	public Logger logger;
 	public int maxProblems;
 	public Map<String, String> options;
-	long complianceLevel;
+	protected long complianceLevel;
 	public char[][] ignoreOptionalProblemsFromFolders;
 	protected PrintWriter out;
 	public boolean proceed = true;
@@ -1842,6 +1844,8 @@
 	final int INSIDE_SYSTEM = 27;
 	final int INSIDE_PROCESSOR_MODULE_PATH_start = 28;
 	final int INSIDE_ADD_MODULES = 29;
+	final int INSIDE_RELEASE = 30;
+	final int INSIDE_LIMIT_MODULES = 31;
 
 	final int DEFAULT = 0;
 	ArrayList<String> bootclasspaths = new ArrayList<>(DEFAULT_SIZE_CLASSPATH);
@@ -2077,6 +2081,10 @@
 					mode = INSIDE_MAX_PROBLEMS;
 					continue;
 				}
+				if (currentArg.equals("--release")) { //$NON-NLS-1$
+					mode = INSIDE_RELEASE;
+					continue;
+				}
 				if (currentArg.equals("-source")) { //$NON-NLS-1$
 					mode = INSIDE_SOURCE;
 					continue;
@@ -2215,6 +2223,10 @@
 					mode = INSIDE_ADD_MODULES;
 					continue;
 				}
+				if (currentArg.equals("--limit-modules")) { //$NON-NLS-1$
+					mode = INSIDE_LIMIT_MODULES;
+					continue;
+				}
 				if (currentArg.equals("-sourcepath")) {//$NON-NLS-1$
 					if (sourcepathClasspathArg != null) {
 						StringBuffer errorMessage = new StringBuffer();
@@ -2666,6 +2678,10 @@
 					throw new IllegalArgumentException(
 						this.bind("configure.duplicateTarget", currentArg));//$NON-NLS-1$
 				}
+				if (this.releaseVersion != null) {
+					throw new IllegalArgumentException(
+							this.bind("configure.unsupportedWithRelease", "-target"));//$NON-NLS-1$ //$NON-NLS-2$
+				}
 				this.didSpecifyTarget = true;
 				if (currentArg.equals("1.1")) { //$NON-NLS-1$
 					this.options.put(CompilerOptions.OPTION_TargetPlatform, CompilerOptions.VERSION_1_1);
@@ -2723,11 +2739,38 @@
 				}
 				mode = DEFAULT;
 				continue;
+			case INSIDE_RELEASE:
+				// If release is < 9, the following are diasllowed:
+				// bootclasspath, -Xbootclasspath, -Xbootclasspath/a:, -Xbootclasspath/p:, 
+				// -endorseddirs, -Djava.endorsed.dirs, -extdirs, -Djava.ext.dirs
+
+				// If release >= 9, the following are disallowed
+				// --system and --upgrade-module-path
+
+				// -source and -target are diasllowed for any --release
+				this.releaseVersion = currentArg;
+				long releaseToJDKLevel = CompilerOptions.releaseToJDKLevel(currentArg);
+				if (releaseToJDKLevel == 0) {
+					throw new IllegalArgumentException(
+							this.bind("configure.unsupportedReleaseVersion", currentArg)); //$NON-NLS-1$
+				}
+				// Let's treat it as regular compliance mode
+				this.complianceLevel = releaseToJDKLevel;
+				String versionAsString = CompilerOptions.versionFromJdkLevel(releaseToJDKLevel);
+				this.options.put(CompilerOptions.OPTION_Compliance, versionAsString);
+				this.options.put(CompilerOptions.OPTION_Source, versionAsString);
+				this.options.put(CompilerOptions.OPTION_TargetPlatform, versionAsString);
+				mode = DEFAULT;
+				continue;
 			case INSIDE_SOURCE :
 				if (this.didSpecifySource) {
 					throw new IllegalArgumentException(
 						this.bind("configure.duplicateSource", currentArg));//$NON-NLS-1$
 				}
+				if (this.releaseVersion != null) {
+					throw new IllegalArgumentException(
+							this.bind("configure.unsupportedWithRelease", "-source"));//$NON-NLS-1$ //$NON-NLS-2$
+				}
 				this.didSpecifySource = true;
 				if (currentArg.equals("1.3")) { //$NON-NLS-1$
 					this.options.put(CompilerOptions.OPTION_Source, CompilerOptions.VERSION_1_3);
@@ -2822,6 +2865,16 @@
 					this.rootModules.add(tokenizer.nextToken().trim());
 				}
 				continue;
+			case INSIDE_LIMIT_MODULES:
+				mode = DEFAULT;
+				tokenizer = new StringTokenizer(currentArg, ","); //$NON-NLS-1$
+				while (tokenizer.hasMoreTokens()) {
+					if (this.limitedModules == null) {
+						this.limitedModules = new HashSet<>();
+					}
+					this.limitedModules.add(tokenizer.nextToken().trim());
+				}
+				continue;
 			case INSIDE_CLASSPATH_start:
 				mode = DEFAULT;
 				index += processPaths(newCommandLineArgs, index, currentArg, classpaths);
@@ -3392,7 +3445,8 @@
 
 public FileSystem getLibraryAccess() {
 	FileSystem nameEnvironment = new FileSystem(this.checkedClasspaths, this.filenames, 
-					this.annotationsFromClasspath && CompilerOptions.ENABLED.equals(this.options.get(CompilerOptions.OPTION_AnnotationBasedNullAnalysis)));
+					this.annotationsFromClasspath && CompilerOptions.ENABLED.equals(this.options.get(CompilerOptions.OPTION_AnnotationBasedNullAnalysis)),
+					this.limitedModules);
 	nameEnvironment.module = this.module;
 	processAddonModuleOptions(nameEnvironment);
 	return nameEnvironment;
@@ -4669,6 +4723,14 @@
 			map.put(qName, m);
 		}
 	}
+	if (this.limitedModules != null) {
+		for (String m : this.limitedModules) {
+			ModuleBinding mod = environment.getModule(m.toCharArray());
+			if (mod == null) {
+				throw new IllegalArgumentException(this.bind("configure.invalidModuleName", m)); //$NON-NLS-1$
+			}
+		}
+	}
 }
 private ReferenceBinding[] processClassNames(LookupEnvironment environment) {
 	// check for .class file presence in case of apt processing
@@ -5088,20 +5150,18 @@
 		String version = this.options.get(CompilerOptions.OPTION_Compliance);
 		this.complianceLevel = CompilerOptions.versionToJdkLevel(version);
 	}
-
-	if (this.complianceLevel > ClassFileConstants.JDK1_8) {
-		if (bootclasspaths != null && bootclasspaths.size() > 0)
-			throw new IllegalArgumentException(
-				this.bind("configure.unsupportedOption", "-bootclasspath")); //$NON-NLS-1$ //$NON-NLS-2$
-		if (extdirsClasspaths != null && extdirsClasspaths.size() > 0)
-			throw new IllegalArgumentException(
-				this.bind("configure.unsupportedOption", "-extdirs")); //$NON-NLS-1$ //$NON-NLS-2$
-		if (endorsedDirClasspaths != null && endorsedDirClasspaths.size() > 0)
-			throw new IllegalArgumentException(
-				this.bind("configure.unsupportedOption", "-endorseddirs")); //$NON-NLS-1$ //$NON-NLS-2$
-	}
 	// process bootclasspath, classpath and sourcepaths
- 	ArrayList<Classpath> allPaths = handleBootclasspath(bootclasspaths, customEncoding);
+	ArrayList<Classpath> allPaths = null;
+	long jdkLevel = validateClasspathOptions(bootclasspaths, endorsedDirClasspaths, extdirsClasspaths);
+
+	if (this.releaseVersion != null && this.complianceLevel < jdkLevel) {
+		// TODO: Revisit for access rules
+		allPaths = new ArrayList<Classpath>();
+		allPaths.add(
+				FileSystem.getOlderSystemRelease(this.javaHomeCache.getAbsolutePath(), this.releaseVersion, null));
+	} else {
+		allPaths = handleBootclasspath(bootclasspaths, customEncoding);
+	}
 
 	List<FileSystem.Classpath> cp = handleClasspath(classpaths, customEncoding);
 
@@ -5165,9 +5225,32 @@
 	}
 	return false;
 }
+protected long validateClasspathOptions(ArrayList<String> bootclasspaths, ArrayList<String> endorsedDirClasspaths, ArrayList<String> extdirsClasspaths) {
+	if (this.complianceLevel > ClassFileConstants.JDK1_8) {
+		if (bootclasspaths != null && bootclasspaths.size() > 0)
+			throw new IllegalArgumentException(
+				this.bind("configure.unsupportedOption", "-bootclasspath")); //$NON-NLS-1$ //$NON-NLS-2$
+		if (extdirsClasspaths != null && extdirsClasspaths.size() > 0)
+			throw new IllegalArgumentException(
+				this.bind("configure.unsupportedOption", "-extdirs")); //$NON-NLS-1$ //$NON-NLS-2$
+		if (endorsedDirClasspaths != null && endorsedDirClasspaths.size() > 0)
+			throw new IllegalArgumentException(
+				this.bind("configure.unsupportedOption", "-endorseddirs")); //$NON-NLS-1$ //$NON-NLS-2$
+	}
+	long jdkLevel = Util.getJDKLevel(getJavaHome());
+	if (jdkLevel < ClassFileConstants.JDK9 && this.releaseVersion != null) {
+		throw new IllegalArgumentException(
+				this.bind("configure.unsupportedReleaseOption")); //$NON-NLS-1$
+	}
+	return jdkLevel;
+}
 protected void validateOptions(boolean didSpecifyCompliance) {
 	if (didSpecifyCompliance) {
-		Object version = this.options.get(CompilerOptions.OPTION_Compliance);
+		String version = this.options.get(CompilerOptions.OPTION_Compliance);
+		if (this.releaseVersion != null) {
+			throw new IllegalArgumentException(
+					this.bind("configure.unsupportedWithRelease", version));//$NON-NLS-1$
+		}
 		if (CompilerOptions.VERSION_1_3.equals(version)) {
 			if (!this.didSpecifySource) this.options.put(CompilerOptions.OPTION_Source, CompilerOptions.VERSION_1_3);
 			if (!this.didSpecifyTarget) this.options.put(CompilerOptions.OPTION_TargetPlatform, CompilerOptions.VERSION_1_1);
diff --git a/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/messages.properties b/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/messages.properties
index e079b45..42e6e62 100644
--- a/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/messages.properties
+++ b/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/messages.properties
@@ -60,6 +60,9 @@
 configure.duplicateCompliance = duplicate compliance setting specification: {0}
 configure.duplicateSource = duplicate source compliance setting specification: {0}
 configure.duplicateTarget = duplicate target compliance setting specification: {0}
+configure.unsupportedReleaseOption = option --release is supported only when run with JDK 9 or above
+configure.unsupportedWithRelease = option {0} is not supported when --release is used
+configure.unsupportedReleaseVersion = release version {0} is not supported
 configure.source = source level should be comprised in between ''1.3'' and ''1.9'' (or ''5'', ''5.0'', ..., ''9'' or ''9.0''): {0}
 configure.invalidSystem = invalid location for system libraries: {0}
 configure.unsupportedOption = option {0} not supported at compliance level 9 and above
@@ -209,7 +212,20 @@
 \                       specify where to find source files for multiple modules\n\
 \    -p --module-path <directories separated by {0}>\n\
 \                       specify where to find application modules\n\
-\    --system <jdk>      Override location of system modules\
+\    --system <jdk>      Override location of system modules\n\
+\    --add-exports <module>/<package>=<other-module>(,<other-module>)*\n\
+\                       specify additional package exports clauses to the\n\
+\                       given modules\n\
+\    --add-reads <module>=<other-module>(,<other-module>)*\n\
+\                       specify additional modules to be considered as required\n\
+\                       by given modules\n\
+\    --add-modules  <module>(,<module>)*\n\
+\                       specify the additional module names that should be\n\
+\                       resolved to be root modules\n\
+\    --limit-modules <module>(,<module>)*\n\
+\                       specify the observable module names\n\
+\    --release <release>\n\
+\                       compile for a specific VM version\n\
 \ \n\
 \ Compliance options:\n\
 \    -1.3               use 1.3 compliance (-source 1.3 -target 1.1)\n\
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/impl/CompilerOptions.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/impl/CompilerOptions.java
index 0b3ee19..4b74849 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/impl/CompilerOptions.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/impl/CompilerOptions.java
@@ -785,6 +785,23 @@
 		return Util.EMPTY_STRING; // unknown version
 	}
 
+	public static long releaseToJDKLevel(String release) {
+		if (release != null && release.length() > 0) {
+			switch(release.charAt(0)) {
+				case '6':
+					return ClassFileConstants.JDK1_6;
+				case '7':
+					return ClassFileConstants.JDK1_7;
+				case '8':
+					return ClassFileConstants.JDK1_8;
+				case '9':
+					return ClassFileConstants.JDK9;
+				default:
+					return 0; // unknown
+			}
+		}
+		return 0;
+	}
 	public static long versionToJdkLevel(String versionID) {
 		String version = versionID;
 		// verification is optimized for all versions with same length and same "1." prefix