Bug 553269 - Eclipse detects changed ENCODING on opening a project with
explicit encoding set

After closing and opening a project we do not expect ENCODING flag to be
in the resource delta send, but the code does exactly that *if* the
project encoding was modified at least once during the current Eclipse
session. This problem was introduced in bug 84350.

ResourceInfo.incrementCharsetGenerationCount() writes two parts of an
int into charsetAndContentId (and
ResourceInfo.getCharsetGenerationCount() uses the upper part), but
ResourceInfo.writeTo(DataOutput) and ResourceInfo.readFrom(int,
DataInput) read/write only the lower part.

So every time Container.setDefaultCharset(String, IProgressMonitor) is
called (for example after reading
.settings/org.eclipse.core.resources.prefs with encoding set), the
"current" (in memory) project info differs from saved/loaded project
info.

Since the "current" project info remains in memory after closing the
project, its charsetAndContentId content will be always different
compared to the data read back from disk.

After Eclipse restart, where *all* the data is loaded from disk,
charsetAndContentId will be consistent (it will not have
"CharsetGenerationCount" part anymore), and the troubles start again
only after someone touches .settings/org.eclipse.core.resources.prefs in
some way (either by changing encoding settings via UI or via git/other
direct resource operations).

Safest way to fix that would be NOT to change the way how the tree is
saved to disk (so do NOT save "CharsetGenerationCount" part of
ResourceInfo.charsetAndContentId), but instead, clean up this part on
closing the project in Project.internalClose(IProgressMonitor), similar
what is done via ResourceInfo.clearModificationStamp().

Change-Id: I77cd8c0af2e3283191e185a8683162b77e544f39
Signed-off-by: Andrey Loskutov <loskutov@gmx.de>
diff --git a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Project.java b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Project.java
index 5a61ab9..f907db7 100644
--- a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Project.java
+++ b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Project.java
@@ -627,6 +627,7 @@
 		info.clear(M_OPEN);
 		info.clearSessionProperties();
 		info.clearModificationStamp();
+		info.clearCharsetGenerationCount();
 		info.setSyncInfo(null);
 	}
 
diff --git a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceInfo.java b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceInfo.java
index 1fbd484..f8c2e7a 100644
--- a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceInfo.java
+++ b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceInfo.java
@@ -116,6 +116,10 @@
 		modStamp = IResource.NULL_STAMP;
 	}
 
+	public void clearCharsetGenerationCount() {
+		charsetAndContentId = getContentId();
+	}
+
 	public synchronized void clearSessionProperties() {
 		sessionProperties = null;
 	}
diff --git a/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/regression/IResourceTest.java b/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/regression/IResourceTest.java
index aa95f69..8e14a6f 100644
--- a/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/regression/IResourceTest.java
+++ b/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/regression/IResourceTest.java
@@ -15,16 +15,18 @@
 package org.eclipse.core.tests.resources.regression;
 
 import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
 import junit.framework.Test;
 import junit.framework.TestSuite;
 import org.eclipse.core.filesystem.EFS;
 import org.eclipse.core.resources.*;
 import org.eclipse.core.runtime.*;
+import org.eclipse.core.tests.resources.ResourceDeltaVerifier;
 import org.eclipse.core.tests.resources.ResourceTest;
 
 public class IResourceTest extends ResourceTest {
 
-	private boolean DISABLED = true;
+	private final boolean DISABLED = true;
 
 	public static Test suite() {
 		return new TestSuite(IResourceTest.class);
@@ -166,7 +168,7 @@
 		final boolean[] seen = new boolean[] {false};
 		final boolean[] phantomSeen = new boolean[] {false};
 		class DeltaVisitor implements IResourceDeltaVisitor {
-			private boolean[] mySeen;
+			private final boolean[] mySeen;
 
 			DeltaVisitor(boolean[] mySeen) {
 				this.mySeen = mySeen;
@@ -695,4 +697,41 @@
 			fail("3.0", e);
 		}
 	}
+
+	/**
+	 * 553269: Eclipse sends unexpected ENCODING change after closing/opening
+	 * project with explicit encoding settings changed in the same session
+	 */
+	public void testBug553269() throws Exception {
+		IWorkspace workspace = getWorkspace();
+		IProject project = workspace.getRoot().getProject("MyProject");
+		IFolder settingsFolder = project.getFolder(".settings");
+		IFile settingsFile = settingsFolder.getFile("org.eclipse.core.resources.prefs");
+		project.create(null);
+		project.open(null);
+		project.setDefaultCharset(StandardCharsets.UTF_8.name(), null);
+
+		assertTrue("Preferences saved", settingsFile.exists());
+
+		project.close(null);
+
+		ResourceDeltaVerifier verifier = new ResourceDeltaVerifier();
+		workspace.addResourceChangeListener(verifier, IResourceChangeEvent.POST_CHANGE);
+		// We expect only OPEN change, the original code generated
+		// IResourceDelta.OPEN | IResourceDelta.ENCODING
+		verifier.addExpectedChange(project, IResourceDelta.CHANGED, IResourceDelta.OPEN);
+
+		// This is irrelevant for the test but verifier verifies entire delta...
+		verifier.addExpectedChange(settingsFolder, IResourceDelta.ADDED, 0);
+		verifier.addExpectedChange(settingsFile, IResourceDelta.ADDED, 0);
+		verifier.addExpectedChange(project.getFile(".project"), IResourceDelta.ADDED, 0);
+
+		try {
+			project.open(null);
+			assertTrue(verifier.getMessage(), verifier.isDeltaValid());
+		} finally {
+			workspace.removeResourceChangeListener(verifier);
+			project.delete(true, true, null);
+		}
+	}
 }