Bug 580009 - Resolve superclass for tc level options in rc configs

Change-Id: I03093b687bc36610ab7cf83d7e75401ac7a4fdfe
diff --git a/build/org.eclipse.cdt.managedbuilder.core.tests/plugin.xml b/build/org.eclipse.cdt.managedbuilder.core.tests/plugin.xml
index a16c798..4611ce2 100644
--- a/build/org.eclipse.cdt.managedbuilder.core.tests/plugin.xml
+++ b/build/org.eclipse.cdt.managedbuilder.core.tests/plugin.xml
@@ -9779,5 +9779,69 @@
          </configuration>
       </projectType>
    </extension>
+   <extension
+         point="org.eclipse.cdt.managedbuilder.core.buildDefinitions">
+      <tool
+            id="bug580009.tests.tool"
+            isAbstract="true"
+            isSystem="true"
+            outputs="o"
+            sources="c">
+         <option
+               category="bug580009.tests.tool.optionsCategory"
+               defaultValue="UNSET"
+               id="bug580009.tests.option.string"
+               isAbstract="false"
+               name="Test"
+               resourceFilter="all"
+               value="UNSET"
+               valueType="string">
+         </option>
+         <optionCategory
+               id="bug580009.tests.tool.optionsCategory"
+               name="name1">
+         </optionCategory>
+      </tool>
+      <projectType
+            id="bug580009.tests.ptype"
+            isAbstract="false"
+            isTest="true">
+         <configuration
+               id="bug580009.tests.cfg1"
+               name="cfg1">
+            <toolChain
+                  id="bug580009.tests.cfg1.tc2"
+                  isAbstract="false"
+                  isSystem="false"
+                  superClass="bug580009.tests.cfg1.tc">
+            </toolChain>
+         </configuration>
+      </projectType>
+      <toolChain
+            id="bug580009.tests.cfg1.tc"
+            isAbstract="true"
+            isSystem="true">
+         <tool
+               id="bug580009.tests.cfg1.tc.tool"
+               isAbstract="false"
+               superClass="bug580009.tests.tool">
+         </tool>
+         <builder
+               id="bug580009.tests.cfg1.tc.builder"
+               isAbstract="false"
+               isVariableCaseSensitive="false">
+         </builder>
+         <option
+               category="bug580009.tests.cfg1.tc.optionCategory1"
+               id="bug580009.tests.cfg1.tc.option.string"
+               isAbstract="false"
+               valueType="boolean">
+         </option>
+         <optionCategory
+               id="bug580009.tests.cfg1.tc.optionCategory1"
+               name="name">
+         </optionCategory>
+      </toolChain>
+   </extension>
 
 </plugin>
diff --git a/build/org.eclipse.cdt.managedbuilder.core.tests/tests/org/eclipse/cdt/managedbuilder/core/tests/ResourceBuildCoreTests.java b/build/org.eclipse.cdt.managedbuilder.core.tests/tests/org/eclipse/cdt/managedbuilder/core/tests/ResourceBuildCoreTests.java
index 5adb0f5..5124f00 100644
--- a/build/org.eclipse.cdt.managedbuilder.core.tests/tests/org/eclipse/cdt/managedbuilder/core/tests/ResourceBuildCoreTests.java
+++ b/build/org.eclipse.cdt.managedbuilder.core.tests/tests/org/eclipse/cdt/managedbuilder/core/tests/ResourceBuildCoreTests.java
@@ -25,10 +25,12 @@
 import org.eclipse.cdt.managedbuilder.core.IOptionCategory;
 import org.eclipse.cdt.managedbuilder.core.IProjectType;
 import org.eclipse.cdt.managedbuilder.core.IResourceConfiguration;
+import org.eclipse.cdt.managedbuilder.core.IResourceInfo;
 import org.eclipse.cdt.managedbuilder.core.ITool;
 import org.eclipse.cdt.managedbuilder.core.ManagedBuildManager;
 import org.eclipse.cdt.managedbuilder.core.ManagedBuilderCorePlugin;
 import org.eclipse.cdt.managedbuilder.core.ManagedCProjectNature;
+import org.eclipse.cdt.managedbuilder.internal.core.ResourceInfo;
 import org.eclipse.cdt.managedbuilder.internal.core.Tool;
 import org.eclipse.cdt.managedbuilder.testplugin.ManagedBuildTestHelper;
 import org.eclipse.core.resources.IFile;
@@ -45,6 +47,7 @@
 import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.NullProgressMonitor;
+import org.junit.Assert;
 
 import junit.framework.Test;
 import junit.framework.TestCase;
@@ -68,6 +71,7 @@
 		TestSuite suite = new TestSuite(ResourceBuildCoreTests.class.getName());
 		suite.addTest(new ResourceBuildCoreTests("testResourceConfigurations"));
 		suite.addTest(new ResourceBuildCoreTests("testResourceConfigurationReset"));
+		suite.addTest(new ResourceBuildCoreTests("testResourceConfiguration_Bug580009"));
 		//		suite.addTest(new ResourceBuildCoreTests("testResourceConfigurationBuildInfo"));
 		//		suite.addTest(new ResourceBuildCoreTests("testResourceRename"));
 		return suite;
@@ -969,4 +973,138 @@
 		removeProject(renamedProjectName2);
 	}
 
+	protected IResourceInfo getResourceConfiguration(final IConfiguration config, final IResource resource) {
+
+		IResourceInfo resInfo = config.getResourceInfo(resource.getProjectRelativePath(), true); // 'true' to ensure exact path
+		if (resInfo == null) {
+			// Resource element for path may not yet exist, force-create it
+			resInfo = config.createFolderInfo(resource.getProjectRelativePath());
+		}
+		return resInfo;
+	}
+
+	/**
+	 * Test that a folder level resource configuration correctly reloads from disk
+	 * @throws Exception
+	 */
+	public void testResourceConfiguration_Bug580009() throws Exception {
+
+		// Create a new project
+		IProject project = null;
+
+		try {
+			project = createProject(projectName);
+
+			// Now associate the builder with the project
+			ManagedBuildTestHelper.addManagedBuildNature(project);
+			IProjectDescription description = project.getDescription();
+			// Make sure it has a managed nature
+			if (description != null) {
+				assertTrue(description.hasNature(ManagedCProjectNature.MNG_NATURE_ID));
+			}
+
+		} catch (CoreException e) {
+			fail("Test failed on project creation: " + e.getLocalizedMessage());
+		}
+
+		// Find the base project type definition
+		IProjectType[] projTypes = ManagedBuildManager.getDefinedProjectTypes();
+		IProjectType projType = ManagedBuildManager.getProjectType("bug580009.tests.ptype");
+		assertNotNull(projType);
+
+		// Create the managed-project for our project
+		IManagedProject newProject = ManagedBuildManager.createManagedProject(project, projType);
+		assertEquals(newProject.getName(), projType.getName());
+		assertFalse(newProject.equals(projType));
+		ManagedBuildManager.setNewProjectVersion(project);
+
+		// Create a folder ('hello')
+		IFolder helloFolder = project.getProject().getFolder("hello");
+		if (!helloFolder.exists()) {
+			helloFolder.create(true, true, null);
+		}
+
+		// Get the configurations and make one of them as default configuration.
+		IConfiguration defaultConfig = null;
+		IConfiguration[] configs = projType.getConfigurations();
+		for (int i = 0; i < configs.length; ++i) {
+			// Make the first configuration the default
+			if (i == 0) {
+				defaultConfig = newProject.createConfiguration(configs[i], projType.getId() + "." + i);
+			} else {
+				newProject.createConfiguration(configs[i], projType.getId() + "." + i);
+			}
+		}
+		ManagedBuildManager.setDefaultConfiguration(project, defaultConfig);
+
+		//Set toolchain level option
+		IOption tcOption = defaultConfig.getToolChain().getOptionById("bug580009.tests.cfg1.tc.option.string");
+		ManagedBuildManager.setOption(defaultConfig, defaultConfig.getToolChain(), tcOption, true);
+		ManagedBuildManager.saveBuildInfo(project, true);
+		// Create Resource Configurations for hello.c
+		var resConfig = getResourceConfiguration(defaultConfig, helloFolder);
+
+		// Get the tools associated with the resource 'hello'.
+		ITool[] resTools = resConfig.getTools();
+		assertNotNull(resTools);
+		assertEquals(1, resTools.length);
+
+		// Get the build properties for the resource hello
+		ITool resTool = resTools[0];
+		String defaultResToolFlags = resTool.getToolFlags();
+
+		// Get the Test Option.
+		IOption resDebugOption = resTool.getOptionById("bug580009.tests.option.string");
+
+		// Get the default value of debug option for resource.
+		String defaultResDebugOptVal = resDebugOption.getStringValue();
+
+		// Now, override the value with "bug580009.tests.option.string"
+		IOption newResDebugOption = ManagedBuildManager.setOption(resConfig, resTool, resDebugOption, "SET");
+
+		// Get the overridden value of test option.
+		String newResDebugOptVal = newResDebugOption.getStringValue();
+		String newResToolFlags = resTool.getToolFlags();
+
+		// Make sure, default and overridden values are different.
+		assertNotSame(defaultResDebugOptVal, newResDebugOptVal);
+
+		//Check the config reports custom settings
+		Assert.assertTrue("hasCustomSettings should be true", ((ResourceInfo) resConfig).hasCustomSettings());
+
+		ManagedBuildManager.saveBuildInfo(project, true);
+
+		//Close project
+		project.close(null);
+		project.open(null);
+
+		//Reload configs
+		defaultConfig = ManagedBuildManager.getBuildInfo(project).getDefaultConfiguration();
+		var resInfo = defaultConfig.getResourceInfo(helloFolder.getProjectRelativePath(), true);
+
+		//Check the config still reports custom settings (sanity check)
+		Assert.assertTrue("hasCustomSettings should be true", ((ResourceInfo) resInfo).hasCustomSettings());
+
+		resTools = resInfo.getTools();
+		resTool = resTools[0];
+		resDebugOption = resTool.getOptionBySuperClassId("bug580009.tests.option.string");
+
+		// Set back to default value
+		IOption newResDebugOption2 = ManagedBuildManager.setOption(resInfo, resTool, resDebugOption, "UNSET");
+
+		//Check the config now reports no custom settings
+		Assert.assertFalse("hasCustomSettings should be false", ((ResourceInfo) resInfo).hasCustomSettings());
+
+		ManagedBuildManager.saveBuildInfo(project, true);
+
+		//Check the resource config no longer exists
+		resInfo = defaultConfig.getResourceInfo(helloFolder.getProjectRelativePath(), true);
+		Assert.assertNull("resInfo should be null", resInfo);
+
+		// Close and remove project.
+		ResourceHelper.joinIndexerBeforeCleanup(getName());
+		project.close(null);
+		removeProject(projectName);
+	}
+
 }
diff --git a/build/org.eclipse.cdt.managedbuilder.core/src/org/eclipse/cdt/managedbuilder/internal/core/Option.java b/build/org.eclipse.cdt.managedbuilder.core/src/org/eclipse/cdt/managedbuilder/internal/core/Option.java
index 74a02ce..dec68cf 100644
--- a/build/org.eclipse.cdt.managedbuilder.core/src/org/eclipse/cdt/managedbuilder/internal/core/Option.java
+++ b/build/org.eclipse.cdt.managedbuilder.core/src/org/eclipse/cdt/managedbuilder/internal/core/Option.java
@@ -17,6 +17,7 @@
  *******************************************************************************/
 package org.eclipse.cdt.managedbuilder.internal.core;
 
+import java.text.MessageFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
@@ -29,6 +30,7 @@
 import org.eclipse.cdt.managedbuilder.core.BuildException;
 import org.eclipse.cdt.managedbuilder.core.IBuildObject;
 import org.eclipse.cdt.managedbuilder.core.IBuildPropertiesRestriction;
+import org.eclipse.cdt.managedbuilder.core.IConfiguration;
 import org.eclipse.cdt.managedbuilder.core.IHoldsOptions;
 import org.eclipse.cdt.managedbuilder.core.IManagedConfigElement;
 import org.eclipse.cdt.managedbuilder.core.IManagedOptionValueHandler;
@@ -511,7 +513,36 @@
 		if (superClassId != null && superClassId.length() > 0) {
 			superClass = ManagedBuildManager.getExtensionOption(superClassId);
 			if (superClass == null) {
-				// TODO:  Report error
+				/*
+				 * This can happen when options are set at the resource level, for a project using a toolchain definition
+				 * where there are options at the toolchain level & one or more of those options is set at a
+				 * non-default value.
+				 *
+				 * In these cases the superclass is set to the option from the parent not the extension's ID
+				 * Workaround this by searching for any missing superclass IDs at on the parent configs toolchain
+				 *
+				 * See the "bug580009.tests.cfg1.tc" definition in org.eclipse.cdt.managedbuilder.core.tests for an example
+				 */
+				IBuildObject parent = this.getParent();
+				if (parent instanceof IToolChain) {
+					IConfiguration config = ((IToolChain) parent).getParent();
+					IOption foundOption = null;
+					if (config != null) {
+						IToolChain parentToolchain = config.getToolChain();
+						if (parentToolchain != null) {
+							foundOption = parentToolchain.getOptionById(superClassId);
+						}
+					}
+					if (foundOption != null) {
+						superClass = foundOption;
+					} else {
+						ManagedBuilderCorePlugin.log(new Status(IStatus.ERROR, ManagedBuilderCorePlugin.PLUGIN_ID,
+								MessageFormat.format("Missing superclass \"{0}\" for \"{1}\"", superClassId, getId()))); //$NON-NLS-1$
+					}
+				} else {
+					ManagedBuilderCorePlugin.log(new Status(IStatus.ERROR, ManagedBuilderCorePlugin.PLUGIN_ID,
+							MessageFormat.format("Missing superclass \"{0}\" for \"{1}\"", superClassId, getId()))); //$NON-NLS-1$
+				}
 			}
 		}