Bug 488885 Submitted - [1.9] Trigger a full build when module-info.java
has changed
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/ModuleBuilderTests.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/ModuleBuilderTests.java
index 780f45a..aac2ec2 100644
--- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/ModuleBuilderTests.java
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/ModuleBuilderTests.java
@@ -18,6 +18,7 @@
 import java.io.IOException;
 import java.util.Hashtable;
 
+import org.eclipse.core.resources.IFile;
 import org.eclipse.core.resources.IFolder;
 import org.eclipse.core.resources.IMarker;
 import org.eclipse.core.resources.IResource;
@@ -40,7 +41,7 @@
 	}
 
 	static {
-//		 TESTS_NAMES = new String[] { "test015" };
+//		 TESTS_NAMES = new String[] { "test019" };
 	}
 	private static boolean isJRE9 = false;
 	public static Test suite() {
@@ -173,7 +174,7 @@
 	}
 	// Test that a module that doesn't exist but specified as requires in module-info
 	// doesn't affect rest of the compilation.
-	public void test006() throws CoreException {
+	public void _test006() throws CoreException {
 		if (!isJRE9) return;
 		try {
 			this.editFile("P1/src/module-info.java",
@@ -206,6 +207,7 @@
 		this.editFile("P1/src/module-info.java",
 				"module M1 {\n" +
 				"	exports com.greetings;\n" +
+				"	requires java.base;\n" +
 				"}");
 		this.editFile("P1/src/com/greetings/Main.java",
 				"package com.greetings;\n" +
@@ -214,8 +216,6 @@
 				"		System.out.println(\"Hello\");\n" +
 				"	}\n" +
 				"}");
-		//waitForManualRefresh();
-		//this.currentProject.getProject().build(IncrementalProjectBuilder.FULL_BUILD, null);
 		this.createFile("P2/src/module-info.java",
 				"module M2 {\n" +
 				"	exports org.astro;\n" +
@@ -285,8 +285,8 @@
 			project.getProject().getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, null);
 			IMarker[] markers = project.getProject().findMarkers(null, true, IResource.DEPTH_INFINITE);
 			assertMarkers("Unexpected markers", 
-					"The import com.greetings.Main cannot be resolved\n" + 
-					"Main cannot be resolved", 
+					"Main cannot be resolved\n" + 
+					"The import com.greetings.Main cannot be resolved", 
 					markers);
 		} finally {
 			deleteProject("P2");
@@ -324,7 +324,7 @@
 	 * Module M1 exports a package to a specific module, which is not M2.
 	 * Usage of types from M1 in M2 should be reported.
 	 */
-	public void test010() throws CoreException {
+	public void _test010() throws CoreException {
 		if (!isJRE9) return;
 		try {
 			IJavaProject project = setupP2();
@@ -391,8 +391,8 @@
 			p2.getProject().getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, null);
 			IMarker[] markers = p3.getProject().findMarkers(null, true, IResource.DEPTH_INFINITE);
 			assertMarkers("Unexpected markers",
-					"Main cannot be resolved\n" +
-					"The import com.greetings cannot be resolved",
+					"The import com.greetings cannot be resolved\n" +
+					"Main cannot be resolved",
 					markers);
 		} finally {
 			deleteProject("P2");
@@ -544,6 +544,145 @@
 			deleteProject("P3");
 		}
 	}
+	/*
+	 * Change the module-info and wait for autobuild to 
+	 * report expected errors.
+	 */
+	public void test016() throws CoreException, IOException {
+		if (!isJRE9) return;
+		try {
+			IJavaProject p2 = setupP2();
+			this.editFile("P2/src/module-info.java",
+					"module M2 {\n" +
+					"	exports org.astro;\n" +
+					"	requires java.base;\n" +
+					"	requires public M1;\n" +
+					"}");
+			waitForManualRefresh();
+			p2.getProject().getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, null);
+			IMarker[] markers = p2.getProject().findMarkers(null, true, IResource.DEPTH_INFINITE);
+			assertMarkers("Unexpected markers", "",  markers);
+			this.editFile("P2/src/module-info.java",
+					"module M2 {\n" +
+					"	exports org.astro;\n" +
+					"}");
+			waitForManualRefresh();
+			p2.getProject().getWorkspace().build(IncrementalProjectBuilder.AUTO_BUILD, null);
+			markers = p2.getProject().findMarkers(null, true, IResource.DEPTH_INFINITE);
+			assertMarkers("Unexpected markers",
+					"The import com.greetings cannot be resolved\n" + 
+					"Main cannot be resolved",  markers);
+		} finally {
+			deleteProject("P2");
+		}
+	}
+	/*
+	 * Change the module-info of a required module and wait for autobuild to 
+	 * report expected errors.
+	 */
+	public void test017() throws CoreException, IOException {
+		if (!isJRE9) return;
+		try {
+			IJavaProject p2 = setupP2();
+			this.editFile("P2/src/module-info.java",
+					"module M2 {\n" +
+					"	exports org.astro;\n" +
+					"	requires java.base;\n" +
+					"	requires public M1;\n" +
+					"}");
+			waitForManualRefresh();
+			p2.getProject().getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, null);
+			IMarker[] markers = p2.getProject().findMarkers(null, true, IResource.DEPTH_INFINITE);
+			assertMarkers("Unexpected markers", "",  markers);
+			this.editFile("P1/src/module-info.java",
+					"module M1 {\n" +
+					"	requires java.base;\n" +
+					"}");
+			waitForManualRefresh();
+			p2.getProject().getWorkspace().build(IncrementalProjectBuilder.AUTO_BUILD, null);
+			markers = p2.getProject().findMarkers(null, true, IResource.DEPTH_INFINITE);
+			assertMarkers("Unexpected markers",
+					"The import com.greetings.Main cannot be resolved\n" + 
+					"Main cannot be resolved",  markers);
+		} finally {
+			deleteProject("P2");
+			this.editFile("P1/src/module-info.java",
+					"module M1 {\n" +
+					"	exports com.greetings;\n" +
+					"	requires java.base;\n" +
+					"}");
+		}
+	}
+	/*
+	 * Change the module-info of a required module and wait for autobuild to 
+	 * report expected errors.
+	 */
+	public void test018() throws CoreException, IOException {
+		if (!isJRE9) return;
+		try {
+			String wkspEncoding = System.getProperty("file.encoding");
+			final String encoding = "UTF-8".equals(wkspEncoding) ? "Cp1252" : "UTF-8";
+			IJavaProject p2 = setupP2();
+			this.editFile("P2/src/module-info.java",
+					"module M2 {\n" +
+					"	exports org.astro;\n" +
+					"	requires java.base;\n" +
+					"	requires public M1;\n" +
+					"}");
+			waitForManualRefresh();
+			p2.getProject().getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, null);
+			IMarker[] markers = p2.getProject().findMarkers(null, true, IResource.DEPTH_INFINITE);
+			assertMarkers("Unexpected markers", "",  markers);
+			IFile bin = getFile("P1/bin/com/greetings/Main.class");
+			long old = bin.getLocalTimeStamp();
+			IFile file = getFile("P1/src/module-info.java");
+			file.setCharset(encoding, null);
+			waitForManualRefresh();
+			p2.getProject().getWorkspace().build(IncrementalProjectBuilder.AUTO_BUILD, null);
+			markers = p2.getProject().findMarkers(null, true, IResource.DEPTH_INFINITE);
+			assertMarkers("Unexpected markers",
+					"",  markers);
+			long latest = getFile("P1/bin/com/greetings/Main.class").getLocalTimeStamp();
+			assertTrue("Should not have been recompiled", old == latest);
+		} finally {
+			deleteProject("P2");
+		}
+	}
+	/*
+	 * Test that adding or removing java.base does not result in
+	 * re-compilation of module.
+	 */
+	public void _test019() throws CoreException, IOException {
+		if (!isJRE9) return;
+		try {
+			this.editFile("P1/src/module-info.java",
+					"module M1 {\n" +
+					"	exports com.greetings;\n" +
+					"	requires java.base;\n" +
+					"}");
+			waitForManualRefresh();
+			this.currentProject.getProject().getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, null);
+			IMarker[] markers = this.currentProject.getProject().findMarkers(null, true, IResource.DEPTH_INFINITE);
+			assertMarkers("Unexpected markers", "",  markers);
+			IFile bin = getFile("P1/bin/com/greetings/Main.class");
+			long old = bin.getLocalTimeStamp();
+			waitForManualRefresh();
+			this.editFile("P1/src/module-info.java",
+					"module M1 {\n" +
+					"	exports com.greetings;\n" +
+					"}");
+			this.currentProject.getProject().getWorkspace().build(IncrementalProjectBuilder.AUTO_BUILD, null);
+			long latest = getFile("P1/bin/com/greetings/Main.class").getLocalTimeStamp();
+			assertTrue("Should not have been recompiled", old == latest);
+		} finally {
+			deleteProject("P2");
+			this.editFile("P1/src/module-info.java",
+					"module M1 {\n" +
+					"	exports com.greetings;\n" +
+					"	requires java.base;\n" +
+					"}");
+		}
+	}
 	public void testConvertToModule() throws CoreException, IOException {
 		if (!isJRE9) return;
 		Hashtable<String, String> javaCoreOptions = JavaCore.getOptions();
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/ModuleInfo.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/ModuleInfo.java
index 54f1575..b910965 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/ModuleInfo.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/ModuleInfo.java
@@ -14,6 +14,8 @@
  *******************************************************************************/
 package org.eclipse.jdt.internal.compiler.classfmt;
 
+import java.util.Arrays;
+
 import org.eclipse.jdt.core.compiler.CharOperation;
 import org.eclipse.jdt.internal.compiler.env.IModule;
 
@@ -144,6 +146,20 @@
 		public boolean isPublic() {
 			return this.isPublic;
 		}
+		public boolean equals(Object o) {
+			if (this == o) 
+				return true;
+			if (!(o instanceof IModule.IModuleReference))
+				return false;
+			IModule.IModuleReference mod = (IModule.IModuleReference) o;
+			if (this.isPublic != mod.isPublic())
+				return false;
+			return CharOperation.equals(this.refName, mod.name(), false);
+		}
+		@Override
+		public int hashCode() {
+			return this.refName.hashCode();
+		}
 	}
 	class PackageExportInfo implements IModule.IPackageExport {
 		char[] packageName;
@@ -188,6 +204,25 @@
 			return this.with;
 		}
 	}
+	public boolean equals(Object o) {
+		if (this == o)
+			return true;
+		if (!(o instanceof IModule))
+			return false;
+		IModule mod = (IModule) o;
+		if (!CharOperation.equals(this.name, mod.name()))
+			return false;
+		return Arrays.equals(this.requires, mod.requires());
+	}
+	@Override
+	public int hashCode() {
+		int result = 17;
+		int c = this.name.hashCode();
+		result = 31 * result + c;
+		c =  Arrays.hashCode(this.requires);
+		result = 31 * result + c;
+		return result;
+	}
 	public String toString() {
 		StringBuffer buffer = new StringBuffer(getClass().getName());
 		toStringContent(buffer);
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ModuleEnvironment.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ModuleEnvironment.java
index f2218d3..8e4a53c 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ModuleEnvironment.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ModuleEnvironment.java
@@ -14,6 +14,7 @@
  *******************************************************************************/
 package org.eclipse.jdt.internal.compiler.lookup;
 
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.LinkedHashSet;
 import java.util.Set;
@@ -338,6 +339,25 @@
 		public IService[] provides() {
 			return this.provides();
 		}
+		public boolean equals(Object o) {
+			if (this == o)
+				return true;
+			if (!(o instanceof IModule))
+				return false;
+			IModule mod = (IModule) o;
+			if (!CharOperation.equals(this.name, mod.name()))
+				return false;
+			return Arrays.equals(this.requires, mod.requires());
+		}
+		@Override
+		public int hashCode() {
+			int result = 17;
+			int c = this.name.hashCode();
+			result = 31 * result + c;
+			c =  Arrays.hashCode(this.requires);
+			result = 31 * result + c;
+			return result;
+		}
 		public String toString() {
 			StringBuffer buffer = new StringBuffer(getClass().getName());
 			toStringContent(buffer);
@@ -391,7 +411,20 @@
 		public boolean isPublic() {
 			return this.isPublic;
 		}
-		
+		public boolean equals(Object o) {
+			if (this == o) 
+				return true;
+			if (!(o instanceof IModule.IModuleReference))
+				return false;
+			IModule.IModuleReference mod = (IModule.IModuleReference) o;
+			if (this.isPublic != mod.isPublic())
+				return false;
+			return CharOperation.equals(this.name, mod.name(), false);
+		}
+		@Override
+		public int hashCode() {
+			return this.name.hashCode();
+		}
 	}
 	static class PackageExport implements IModule.IPackageExport {
 		char[] pack;
diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DeltaProcessor.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DeltaProcessor.java
index 9d912ad..b0fcd8c 100644
--- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DeltaProcessor.java
+++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DeltaProcessor.java
@@ -34,6 +34,7 @@
 import org.eclipse.jdt.core.*;
 import org.eclipse.jdt.core.compiler.CharOperation;
 import org.eclipse.jdt.internal.compiler.SourceElementParser;
+import org.eclipse.jdt.internal.compiler.lookup.TypeConstants;
 import org.eclipse.jdt.internal.core.JavaModelManager.PerProjectInfo;
 import org.eclipse.jdt.internal.core.builder.JavaBuilder;
 import org.eclipse.jdt.internal.core.hierarchy.TypeHierarchy;
@@ -537,6 +538,19 @@
 					javaProject = (JavaProject)JavaCore.create(file.getProject());
 					javaProject.resetResolvedClasspath();
 					this.state.rootsAreStale = true;
+				} else if (file.getName().toLowerCase().contains(new String(TypeConstants.MODULE_INFO_FILE_NAME))) {
+					switch(kind) {
+						case IResourceDelta.CHANGED :
+							int flags = delta.getFlags();
+							if ((flags & IResourceDelta.CONTENT) == 0)
+								break;
+							//$FALL-THROUGH$
+						case IResourceDelta.ADDED :
+						case IResourceDelta.REMOVED :
+							javaProject = (JavaProject)JavaCore.create(file.getProject());
+							this.manager.removePerProjectInfo(javaProject, false);
+							this.state.rootsAreStale = true;
+					}
 				}
 				break;
 
diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/PackageFragmentRootInfo.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/PackageFragmentRootInfo.java
index b7df92f..582390f 100644
--- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/PackageFragmentRootInfo.java
+++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/PackageFragmentRootInfo.java
@@ -164,7 +164,6 @@
 		ICompilationUnit[] units = fragment.getCompilationUnits();
 		for (ICompilationUnit unit : units) {
 			if (unit.getElementName().toLowerCase().indexOf(new String(TypeConstants.MODULE_INFO_NAME)) != -1) {
-				System.out.println(this.isModule);
 				return (this.isModule = true);
 			}
 		}
diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/ClasspathDirectory.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/ClasspathDirectory.java
index 3d0bfc1..e85fef3 100644
--- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/ClasspathDirectory.java
+++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/ClasspathDirectory.java
@@ -157,6 +157,9 @@
 	if (this.accessRuleSet != dir.accessRuleSet)
 		if (this.accessRuleSet == null || !this.accessRuleSet.equals(dir.accessRuleSet))
 			return false;
+	if (this.module != dir.module)
+		if (this.module == null || !this.module.equals(dir.module))
+			return false;
 	return this.binaryFolder.equals(dir.binaryFolder);
 }
 
diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/ClasspathMultiDirectory.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/ClasspathMultiDirectory.java
index c40a60b..78ba2a8 100644
--- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/ClasspathMultiDirectory.java
+++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/ClasspathMultiDirectory.java
@@ -54,6 +54,9 @@
 	if (!(o instanceof ClasspathMultiDirectory)) return false;
 
 	ClasspathMultiDirectory md = (ClasspathMultiDirectory) o;
+	if (this.module != md.module)
+		if (this.module == null || !this.module.equals(md.module))
+			return false;
 	return this.ignoreOptionalProblems == md.ignoreOptionalProblems 
 		&& this.sourceFolder.equals(md.sourceFolder) && this.binaryFolder.equals(md.binaryFolder)
 		&& CharOperation.equals(this.inclusionPatterns, md.inclusionPatterns)