+ * Expected to be created via the {@link ServiceContextFactory}.
+ */
public abstract class AppEngineProjectWizard extends Wizard implements INewWizard {
protected AppEngineWizardPage page = null;
protected final AppEngineProjectConfig config = new AppEngineProjectConfig();
private IWorkbench workbench;
+ @Inject
+ protected IConfigurationElement configElement;
+
public AppEngineProjectWizard() {
setNeedsProgressMonitor(true);
}
@@ -97,8 +110,12 @@ public boolean performFinish() {
boolean cancelable = true;
getContainer().run(fork, cancelable, runnable);
+ // prompt to switch to preferred perspective
+ BasicNewProjectResourceWizard.updatePerspective(configElement);
+
// open most important file created by wizard in editor
IFile file = runnable.getMostImportant();
+ BasicNewProjectResourceWizard.selectAndReveal(file, workbench.getActiveWorkbenchWindow());
WorkbenchUtil.openInEditor(workbench, file);
return true;
} catch (InterruptedException ex) {
diff --git a/plugins/com.google.cloud.tools.eclipse.integration.appengine/src/com/google/cloud/tools/eclipse/integration/appengine/BaseProjectTest.java b/plugins/com.google.cloud.tools.eclipse.integration.appengine/src/com/google/cloud/tools/eclipse/integration/appengine/BaseProjectTest.java
index db6036af80..737e03a8cb 100644
--- a/plugins/com.google.cloud.tools.eclipse.integration.appengine/src/com/google/cloud/tools/eclipse/integration/appengine/BaseProjectTest.java
+++ b/plugins/com.google.cloud.tools.eclipse.integration.appengine/src/com/google/cloud/tools/eclipse/integration/appengine/BaseProjectTest.java
@@ -25,6 +25,7 @@
import org.eclipse.swtbot.swt.finder.exceptions.WidgetNotFoundException;
import org.eclipse.swtbot.swt.finder.widgets.TimeoutException;
import org.junit.After;
+import org.junit.Before;
import org.junit.BeforeClass;
/**
@@ -36,7 +37,7 @@ public class BaseProjectTest {
protected IProject project;
@BeforeClass
- public static void setUp() throws Exception {
+ public static void setUpWorkbench() throws Exception {
// verify we can find the Google Cloud SDK
new CloudSdk.Builder().build().validateCloudSdk();
@@ -48,6 +49,13 @@ public static void setUp() throws Exception {
}
}
+ @Before
+ public void setUp() {
+ // switch to J2EE to avoid new-project switch-perspective prompts
+ bot.perspectiveById("org.eclipse.jst.j2ee.J2EEPerspective").activate();
+
+ }
+
@After
public void tearDown() {
if (project != null) {
@@ -60,6 +68,7 @@ public void tearDown() {
SwtBotProjectActions.deleteProject(bot, project.getName());
} catch (TimeoutException ex) {
// If this fails it shouldn't fail the test, which has already run
+ new RuntimeException("tearDown() timed out", ex).printStackTrace();
}
project = null;
}
diff --git a/plugins/com.google.cloud.tools.eclipse.integration.appengine/src/com/google/cloud/tools/eclipse/integration/appengine/DebugNativeAppEngineStandardProjectTest.java b/plugins/com.google.cloud.tools.eclipse.integration.appengine/src/com/google/cloud/tools/eclipse/integration/appengine/DebugNativeAppEngineStandardProjectTest.java
index a4cbdcd561..f21a04833c 100644
--- a/plugins/com.google.cloud.tools.eclipse.integration.appengine/src/com/google/cloud/tools/eclipse/integration/appengine/DebugNativeAppEngineStandardProjectTest.java
+++ b/plugins/com.google.cloud.tools.eclipse.integration.appengine/src/com/google/cloud/tools/eclipse/integration/appengine/DebugNativeAppEngineStandardProjectTest.java
@@ -118,7 +118,10 @@ public void run() {
final SWTBotStyledText consoleContents =
new SWTBotStyledText(bot.widget(widgetOfType(StyledText.class), consoleView.getWidget()));
SwtBotTestingUtilities.waitUntilStyledTextContains(bot,
- "Module instance default is running at http://localhost:8080", consoleContents);
+ "Dev App Server is now running", consoleContents);
+
+ System.out.printf("---- Dev App Server ----\n%s\n------------------\n",
+ consoleContents.getText());
// Server is now running
assertEquals("Hello App Engine!",
diff --git a/plugins/com.google.cloud.tools.eclipse.integration.appengine/src/com/google/cloud/tools/eclipse/integration/appengine/NewMavenBasedAppEngineProjectWizardTest.java b/plugins/com.google.cloud.tools.eclipse.integration.appengine/src/com/google/cloud/tools/eclipse/integration/appengine/NewMavenBasedAppEngineProjectWizardTest.java
index 61da67833c..5be512a0f3 100644
--- a/plugins/com.google.cloud.tools.eclipse.integration.appengine/src/com/google/cloud/tools/eclipse/integration/appengine/NewMavenBasedAppEngineProjectWizardTest.java
+++ b/plugins/com.google.cloud.tools.eclipse.integration.appengine/src/com/google/cloud/tools/eclipse/integration/appengine/NewMavenBasedAppEngineProjectWizardTest.java
@@ -65,7 +65,7 @@ public class NewMavenBasedAppEngineProjectWizardTest extends BaseProjectTest {
public void testHelloWorld_java7() throws Exception {
String[] projectFiles =
{"src/main/webapp/WEB-INF/appengine-web.xml", "src/main/webapp/WEB-INF/web.xml", "pom.xml"};
- createAndCheck("appWithPackageProject", null, "com.example.baz",
+ createAndCheck("appWithPackageProjectJava7", null, "com.example.baz",
AppEngineRuntime.STANDARD_JAVA_7, projectFiles);
assertEquals("1.7", getPomProperty(project, "maven.compiler.source"));
assertEquals("1.7", getPomProperty(project, "maven.compiler.target"));
@@ -76,7 +76,7 @@ public void testHelloWorld_java8() throws Exception {
Assume.assumeTrue("Requires a Java 8 JRE", JavaRuntimeUtils.hasJavaSE8());
String[] projectFiles =
{"src/main/webapp/WEB-INF/appengine-web.xml", "src/main/webapp/WEB-INF/web.xml", "pom.xml"};
- createAndCheck("appWithPackageProject", null, "com.example.baz",
+ createAndCheck("appWithPackageProjectJava8", null, "com.example.baz",
AppEngineRuntime.STANDARD_JAVA_8, projectFiles);
assertEquals("1.8", getPomProperty(project, "maven.compiler.source"));
assertEquals("1.8", getPomProperty(project, "maven.compiler.target"));
@@ -90,7 +90,7 @@ public void testHelloWorld_nonDefaultLocation() throws Exception {
String[] projectFiles =
{"src/main/webapp/WEB-INF/appengine-web.xml", "src/main/webapp/WEB-INF/web.xml", "pom.xml"};
- createAndCheck("appWithPackageProjectInTemp", location.getAbsolutePath(), "com.example.foo",
+ createAndCheck("appWithPackageProjectJava7InTemp", location.getAbsolutePath(), "com.example.foo",
AppEngineRuntime.STANDARD_JAVA_7, projectFiles);
}
diff --git a/plugins/com.google.cloud.tools.eclipse.util/src/com/google/cloud/tools/eclipse/util/service/ServiceContextFactory.java b/plugins/com.google.cloud.tools.eclipse.util/src/com/google/cloud/tools/eclipse/util/service/ServiceContextFactory.java
index 7cdc65be35..c1eb91fae4 100644
--- a/plugins/com.google.cloud.tools.eclipse.util/src/com/google/cloud/tools/eclipse/util/service/ServiceContextFactory.java
+++ b/plugins/com.google.cloud.tools.eclipse.util/src/com/google/cloud/tools/eclipse/util/service/ServiceContextFactory.java
@@ -16,6 +16,7 @@
package com.google.cloud.tools.eclipse.util.service;
+import com.google.cloud.tools.eclipse.util.status.StatusUtil;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExecutableExtension;
@@ -28,38 +29,42 @@
import org.osgi.framework.BundleContext;
import org.osgi.framework.FrameworkUtil;
-import com.google.cloud.tools.eclipse.util.status.StatusUtil;
-
public class ServiceContextFactory implements IExecutableExtensionFactory, IExecutableExtension {
private Class> clazz;
+ private IConfigurationElement configElement;
@Override
public void setInitializationData(IConfigurationElement config, String propertyName, Object data)
throws CoreException {
- if (data == null || !(data instanceof String)) {
- throw new CoreException(StatusUtil.error(getClass(), "Data must be a class name"));
- }
- String className = (String) data;
- String bundleSymbolicName = config.getNamespaceIdentifier();
- Bundle bundle = Platform.getBundle(bundleSymbolicName);
- if (bundle == null) {
- throw new CoreException(StatusUtil.error(this, "Missing bundle " + bundleSymbolicName));
- }
- try {
- clazz = bundle.loadClass(className);
- } catch (ClassNotFoundException ex) {
- throw new CoreException(StatusUtil.error(this,
- "Could not load class " + className
- + " from bundle " + bundle.getSymbolicName(),
- ex));
- }
+ if (data == null || !(data instanceof String)) {
+ throw new CoreException(StatusUtil.error(getClass(), "Data must be a class name"));
+ }
+ configElement = config;
+ String className = (String) data;
+ String bundleSymbolicName = config.getNamespaceIdentifier();
+ Bundle bundle = Platform.getBundle(bundleSymbolicName);
+ if (bundle == null) {
+ throw new CoreException(StatusUtil.error(this, "Missing bundle " + bundleSymbolicName));
+ }
+ try {
+ clazz = bundle.loadClass(className);
+ } catch (ClassNotFoundException ex) {
+ throw new CoreException(StatusUtil.error(this,
+ "Could not load class " + className + " from bundle " + bundle.getSymbolicName(), ex));
+ }
}
@Override
public Object create() throws CoreException {
BundleContext bundleContext = FrameworkUtil.getBundle(clazz).getBundleContext();
IEclipseContext serviceContext = EclipseContextFactory.getServiceContext(bundleContext);
- return ContextInjectionFactory.make(clazz, serviceContext);
+ IEclipseContext staticContext = EclipseContextFactory.create();
+ staticContext.set(IConfigurationElement.class, configElement);
+ try {
+ return ContextInjectionFactory.make(clazz, serviceContext, staticContext);
+ } finally {
+ staticContext.dispose();
+ }
}
}
diff --git a/pom.xml b/pom.xml
index b596280fac..546094f659 100644
--- a/pom.xml
+++ b/pom.xml
@@ -398,6 +398,7 @@
eclipse/mars
+ third_party/patches/mars
@@ -471,6 +472,7 @@
eclipse/neon
+ third_party/patches/neon
@@ -505,6 +507,7 @@
eclipse/oxygen
+ third_party/patches/oxygen
diff --git a/third_party/patches/README.md b/third_party/patches/README.md
new file mode 100644
index 0000000000..9bf54a63ab
--- /dev/null
+++ b/third_party/patches/README.md
@@ -0,0 +1,2 @@
+This directory contains patched bundles from Eclipse Mars, Neon, and Oxygen.
+These fixes are only for testing.
diff --git a/third_party/patches/mars/org.eclipse.core.jobs/.classpath b/third_party/patches/mars/org.eclipse.core.jobs/.classpath
new file mode 100644
index 0000000000..e8ea977a69
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.jobs/.classpath
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/third_party/patches/mars/org.eclipse.core.jobs/.options b/third_party/patches/mars/org.eclipse.core.jobs/.options
new file mode 100644
index 0000000000..801e8226eb
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.jobs/.options
@@ -0,0 +1,19 @@
+# Debugging options for the org.eclipse.core.jobs bundle
+
+# NOTE: There is a deadlock risk when using these debug flags in a workspace
+# launched from Eclipse due to interaction with a lock in the debugger console.
+# For details: https://bugs.eclipse.org/bugs/show_bug.cgi?id=93968
+
+# Prints debug information on running background jobs
+org.eclipse.core.jobs/jobs=false
+# Includes current date and time in job debug information
+org.eclipse.core.jobs/jobs/timing=false
+# Prints debug information when scheduling rules begin and end, and for mismatched beginRule/endRule pairs
+org.eclipse.core.jobs/jobs/beginend=false
+# Pedantic assertion checking on locks and deadlock reporting
+org.eclipse.core.jobs/jobs/locks=false
+# Throws an IllegalStateException when deadlock occurs
+org.eclipse.core.jobs/jobs/errorondeadlock=false
+# Debug shutdown behaviour
+org.eclipse.core.jobs/jobs/shutdown=false
+
diff --git a/third_party/patches/mars/org.eclipse.core.jobs/.project b/third_party/patches/mars/org.eclipse.core.jobs/.project
new file mode 100644
index 0000000000..eac5e6eeee
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.jobs/.project
@@ -0,0 +1,34 @@
+
+
+ org.eclipse.core.jobs
+
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ org.eclipse.pde.ManifestBuilder
+
+
+
+
+ org.eclipse.pde.SchemaBuilder
+
+
+
+
+ org.eclipse.pde.api.tools.apiAnalysisBuilder
+
+
+
+
+
+ org.eclipse.pde.PluginNature
+ org.eclipse.jdt.core.javanature
+ org.eclipse.pde.api.tools.apiAnalysisNature
+
+
diff --git a/third_party/patches/mars/org.eclipse.core.jobs/.settings/org.eclipse.core.resources.prefs b/third_party/patches/mars/org.eclipse.core.jobs/.settings/org.eclipse.core.resources.prefs
new file mode 100644
index 0000000000..16532b29ee
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.jobs/.settings/org.eclipse.core.resources.prefs
@@ -0,0 +1,3 @@
+#Tue May 25 15:00:03 EDT 2004
+encoding/=ISO-8859-1
+eclipse.preferences.version=1
diff --git a/third_party/patches/mars/org.eclipse.core.jobs/.settings/org.eclipse.core.runtime.prefs b/third_party/patches/mars/org.eclipse.core.jobs/.settings/org.eclipse.core.runtime.prefs
new file mode 100644
index 0000000000..5a0ad22d2a
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.jobs/.settings/org.eclipse.core.runtime.prefs
@@ -0,0 +1,2 @@
+eclipse.preferences.version=1
+line.separator=\n
diff --git a/third_party/patches/mars/org.eclipse.core.jobs/.settings/org.eclipse.jdt.core.prefs b/third_party/patches/mars/org.eclipse.core.jobs/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000000..739b00f594
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.jobs/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,398 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled
+org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore
+org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull
+org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault
+org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable
+org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.7
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.doc.comment.support=enabled
+org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
+org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning
+org.eclipse.jdt.core.compiler.problem.deadCode=warning
+org.eclipse.jdt.core.compiler.problem.deprecation=warning
+org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
+org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
+org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
+org.eclipse.jdt.core.compiler.problem.emptyStatement=warning
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore
+org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore
+org.eclipse.jdt.core.compiler.problem.fatalOptionalError=enabled
+org.eclipse.jdt.core.compiler.problem.fieldHiding=warning
+org.eclipse.jdt.core.compiler.problem.finalParameterBound=ignore
+org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=error
+org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning
+org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled
+org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
+org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore
+org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=warning
+org.eclipse.jdt.core.compiler.problem.invalidJavadoc=error
+org.eclipse.jdt.core.compiler.problem.invalidJavadocTags=enabled
+org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsDeprecatedRef=disabled
+org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsNotVisibleRef=enabled
+org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsVisibility=private
+org.eclipse.jdt.core.compiler.problem.localVariableHiding=warning
+org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning
+org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore
+org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore
+org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled
+org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=error
+org.eclipse.jdt.core.compiler.problem.missingJavadocComments=ignore
+org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsVisibility=public
+org.eclipse.jdt.core.compiler.problem.missingJavadocTags=ignore
+org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=protected
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=error
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
+org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning
+org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore
+org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning
+org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
+org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=warning
+org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning
+org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error
+org.eclipse.jdt.core.compiler.problem.nullReference=warning
+org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error
+org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning
+org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
+org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
+org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=warning
+org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore
+org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore
+org.eclipse.jdt.core.compiler.problem.rawTypeReference=ignore
+org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning
+org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore
+org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore
+org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore
+org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
+org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning
+org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
+org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
+org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled
+org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=warning
+org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
+org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=disabled
+org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
+org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
+org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=warning
+org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.unnecessaryElse=warning
+org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning
+org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore
+org.eclipse.jdt.core.compiler.problem.unusedImport=warning
+org.eclipse.jdt.core.compiler.problem.unusedLabel=warning
+org.eclipse.jdt.core.compiler.problem.unusedLocal=error
+org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore
+org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore
+org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
+org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=error
+org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore
+org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
+org.eclipse.jdt.core.compiler.source=1.7
+org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_assignment=0
+org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
+org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80
+org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0
+org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
+org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0
+org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
+org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80
+org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
+org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16
+org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
+org.eclipse.jdt.core.formatter.blank_lines_after_package=1
+org.eclipse.jdt.core.formatter.blank_lines_before_field=0
+org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
+org.eclipse.jdt.core.formatter.blank_lines_before_imports=1
+org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1
+org.eclipse.jdt.core.formatter.blank_lines_before_method=1
+org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
+org.eclipse.jdt.core.formatter.blank_lines_before_package=0
+org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
+org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1
+org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_lambda_body=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
+org.eclipse.jdt.core.formatter.comment.format_block_comments=true
+org.eclipse.jdt.core.formatter.comment.format_header=false
+org.eclipse.jdt.core.formatter.comment.format_html=true
+org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
+org.eclipse.jdt.core.formatter.comment.format_line_comments=true
+org.eclipse.jdt.core.formatter.comment.format_source_code=true
+org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true
+org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
+org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
+org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=insert
+org.eclipse.jdt.core.formatter.comment.line_length=80
+org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true
+org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true
+org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false
+org.eclipse.jdt.core.formatter.compact_else_if=true
+org.eclipse.jdt.core.formatter.continuation_indentation=2
+org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
+org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off
+org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on
+org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
+org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
+org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
+org.eclipse.jdt.core.formatter.indent_empty_lines=false
+org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
+org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
+org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
+org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false
+org.eclipse.jdt.core.formatter.indentation.size=4
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
+org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
+org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow=insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert
+org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
+org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
+org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
+org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
+org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.join_lines_in_comments=true
+org.eclipse.jdt.core.formatter.join_wrapped_lines=true
+org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
+org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
+org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false
+org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
+org.eclipse.jdt.core.formatter.lineSplit=120
+org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
+org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
+org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
+org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1
+org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true
+org.eclipse.jdt.core.formatter.tabulation.char=tab
+org.eclipse.jdt.core.formatter.tabulation.size=4
+org.eclipse.jdt.core.formatter.use_on_off_tags=false
+org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
+org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
+org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true
+org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true
+org.eclipse.jdt.core.javaFormatter=org.eclipse.jdt.core.defaultJavaFormatter
diff --git a/third_party/patches/mars/org.eclipse.core.jobs/.settings/org.eclipse.jdt.ui.prefs b/third_party/patches/mars/org.eclipse.core.jobs/.settings/org.eclipse.jdt.ui.prefs
new file mode 100644
index 0000000000..e8333bda65
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.jobs/.settings/org.eclipse.jdt.ui.prefs
@@ -0,0 +1,128 @@
+cleanup.add_default_serial_version_id=true
+cleanup.add_generated_serial_version_id=false
+cleanup.add_missing_annotations=false
+cleanup.add_missing_deprecated_annotations=true
+cleanup.add_missing_methods=false
+cleanup.add_missing_nls_tags=false
+cleanup.add_missing_override_annotations=true
+cleanup.add_missing_override_annotations_interface_methods=true
+cleanup.add_serial_version_id=false
+cleanup.always_use_blocks=true
+cleanup.always_use_parentheses_in_expressions=false
+cleanup.always_use_this_for_non_static_field_access=false
+cleanup.always_use_this_for_non_static_method_access=false
+cleanup.convert_functional_interfaces=false
+cleanup.convert_to_enhanced_for_loop=false
+cleanup.correct_indentation=false
+cleanup.format_source_code=false
+cleanup.format_source_code_changes_only=false
+cleanup.insert_inferred_type_arguments=false
+cleanup.make_local_variable_final=true
+cleanup.make_parameters_final=false
+cleanup.make_private_fields_final=true
+cleanup.make_type_abstract_if_missing_method=false
+cleanup.make_variable_declarations_final=false
+cleanup.never_use_blocks=false
+cleanup.never_use_parentheses_in_expressions=true
+cleanup.organize_imports=false
+cleanup.qualify_static_field_accesses_with_declaring_class=false
+cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
+cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
+cleanup.qualify_static_member_accesses_with_declaring_class=true
+cleanup.qualify_static_method_accesses_with_declaring_class=false
+cleanup.remove_private_constructors=true
+cleanup.remove_redundant_type_arguments=true
+cleanup.remove_trailing_whitespaces=true
+cleanup.remove_trailing_whitespaces_all=true
+cleanup.remove_trailing_whitespaces_ignore_empty=false
+cleanup.remove_unnecessary_casts=true
+cleanup.remove_unnecessary_nls_tags=true
+cleanup.remove_unused_imports=false
+cleanup.remove_unused_local_variables=false
+cleanup.remove_unused_private_fields=true
+cleanup.remove_unused_private_members=false
+cleanup.remove_unused_private_methods=true
+cleanup.remove_unused_private_types=true
+cleanup.sort_members=false
+cleanup.sort_members_all=false
+cleanup.use_anonymous_class_creation=false
+cleanup.use_blocks=false
+cleanup.use_blocks_only_for_return_and_throw=false
+cleanup.use_lambda=true
+cleanup.use_parentheses_in_expressions=false
+cleanup.use_this_for_non_static_field_access=false
+cleanup.use_this_for_non_static_field_access_only_if_necessary=true
+cleanup.use_this_for_non_static_method_access=false
+cleanup.use_this_for_non_static_method_access_only_if_necessary=true
+cleanup.use_type_arguments=false
+cleanup_profile=_Whitespace_remove
+cleanup_settings_version=2
+eclipse.preferences.version=1
+editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
+formatter_profile=org.eclipse.jdt.ui.default.eclipse_profile
+formatter_settings_version=12
+internal.default.compliance=default
+org.eclipse.jdt.ui.ignorelowercasenames=true
+org.eclipse.jdt.ui.importorder=;
+org.eclipse.jdt.ui.javadoc=false
+org.eclipse.jdt.ui.ondemandthreshold=3
+org.eclipse.jdt.ui.staticondemandthreshold=99
+org.eclipse.jdt.ui.text.custom_code_templates=/**\n * @return Returns the ${bare_field_name}.\n *//**\n * @param ${param} The ${bare_field_name} to set.\n *//**\n * ${tags}\n *//*******************************************************************************\n * Copyright (c) ${year} IBM Corporation and others.\n * All rights reserved. This program and the accompanying materials\n * are made available under the terms of the Eclipse Public License v1.0\n * which accompanies this distribution, and is available at\n * http\://www.eclipse.org/legal/epl-v10.html\n *\n * Contributors\:\n * IBM Corporation - initial API and implementation\n ******************************************************************************/\n/**\n * @since 3.2\n *\n * ${tags}\n *//**\n * \n *//**\n * ${tags}\n *//* (non-Javadoc)\n * ${see_to_overridden}\n *//*******************************************************************************\n * Copyright (c) ${year} IBM Corporation and others.\n * All rights reserved. This program and the accompanying materials\n * are made available under the terms of the Eclipse Public License v1.0\n * which accompanies this distribution, and is available at\n * http\://www.eclipse.org/legal/epl-v10.html\n * \n * Contributors\:\n * IBM Corporation - initial API and implementation\n *******************************************************************************/\n${filecomment}\n${package_declaration}\n\n/**\n * \n */\n${type_declaration}\n\n\n\n// ${todo} Auto-generated catch block&\#13;\n${exception_var}.printStackTrace();${body_statement}${body_statement}return ${field};${field} \= ${param};/**\n * ${tags}\n * ${see_to_target}\n */
+sp_cleanup.add_default_serial_version_id=true
+sp_cleanup.add_generated_serial_version_id=false
+sp_cleanup.add_missing_annotations=false
+sp_cleanup.add_missing_deprecated_annotations=true
+sp_cleanup.add_missing_methods=false
+sp_cleanup.add_missing_nls_tags=false
+sp_cleanup.add_missing_override_annotations=true
+sp_cleanup.add_missing_override_annotations_interface_methods=false
+sp_cleanup.add_serial_version_id=false
+sp_cleanup.always_use_blocks=true
+sp_cleanup.always_use_parentheses_in_expressions=false
+sp_cleanup.always_use_this_for_non_static_field_access=false
+sp_cleanup.always_use_this_for_non_static_method_access=false
+sp_cleanup.convert_functional_interfaces=false
+sp_cleanup.convert_to_enhanced_for_loop=false
+sp_cleanup.correct_indentation=false
+sp_cleanup.format_source_code=true
+sp_cleanup.format_source_code_changes_only=true
+sp_cleanup.insert_inferred_type_arguments=false
+sp_cleanup.make_local_variable_final=false
+sp_cleanup.make_parameters_final=false
+sp_cleanup.make_private_fields_final=true
+sp_cleanup.make_type_abstract_if_missing_method=false
+sp_cleanup.make_variable_declarations_final=false
+sp_cleanup.never_use_blocks=false
+sp_cleanup.never_use_parentheses_in_expressions=true
+sp_cleanup.on_save_use_additional_actions=true
+sp_cleanup.organize_imports=true
+sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
+sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
+sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
+sp_cleanup.remove_private_constructors=true
+sp_cleanup.remove_redundant_type_arguments=false
+sp_cleanup.remove_trailing_whitespaces=true
+sp_cleanup.remove_trailing_whitespaces_all=true
+sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
+sp_cleanup.remove_unnecessary_casts=false
+sp_cleanup.remove_unnecessary_nls_tags=false
+sp_cleanup.remove_unused_imports=false
+sp_cleanup.remove_unused_local_variables=false
+sp_cleanup.remove_unused_private_fields=true
+sp_cleanup.remove_unused_private_members=false
+sp_cleanup.remove_unused_private_methods=true
+sp_cleanup.remove_unused_private_types=true
+sp_cleanup.sort_members=false
+sp_cleanup.sort_members_all=false
+sp_cleanup.use_anonymous_class_creation=false
+sp_cleanup.use_blocks=false
+sp_cleanup.use_blocks_only_for_return_and_throw=false
+sp_cleanup.use_lambda=false
+sp_cleanup.use_parentheses_in_expressions=false
+sp_cleanup.use_this_for_non_static_field_access=false
+sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
+sp_cleanup.use_this_for_non_static_method_access=false
+sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true
+sp_cleanup.use_type_arguments=false
diff --git a/third_party/patches/mars/org.eclipse.core.jobs/META-INF/MANIFEST.MF b/third_party/patches/mars/org.eclipse.core.jobs/META-INF/MANIFEST.MF
new file mode 100644
index 0000000000..2f134c3380
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.jobs/META-INF/MANIFEST.MF
@@ -0,0 +1,18 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: org.eclipse.core.jobs patched for bug 478634
+Bundle-SymbolicName: org.eclipse.core.jobs; singleton:=true
+Bundle-Version: 3.7.0.v20150330-2103
+Bundle-Vendor: %providerName
+Bundle-Localization: plugin
+Export-Package: org.eclipse.core.internal.jobs;x-internal:=true,
+ org.eclipse.core.runtime.jobs
+Bundle-Activator: org.eclipse.core.internal.jobs.JobActivator
+Require-Bundle: org.eclipse.equinox.common;bundle-version="[3.2.0,4.0.0)"
+Bundle-RequiredExecutionEnvironment: JavaSE-1.7
+Bundle-ActivationPolicy: lazy
+Import-Package: org.eclipse.osgi.service.debug,
+ org.eclipse.osgi.util,
+ org.osgi.framework;version="1.3.0",
+ org.osgi.service.packageadmin,
+ org.osgi.util.tracker
diff --git a/third_party/patches/mars/org.eclipse.core.jobs/about.html b/third_party/patches/mars/org.eclipse.core.jobs/about.html
new file mode 100644
index 0000000000..460233046e
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.jobs/about.html
@@ -0,0 +1,28 @@
+
+
+
+
+About
+
+
+
About This Content
+
+
June 2, 2006
+
License
+
+
The Eclipse Foundation makes available all content in this plug-in ("Content"). Unless otherwise
+indicated below, the Content is provided to you under the terms and conditions of the
+Eclipse Public License Version 1.0 ("EPL"). A copy of the EPL is available
+at http://www.eclipse.org/legal/epl-v10.html.
+For purposes of the EPL, "Program" will mean the Content.
+
+
If you did not receive this Content directly from the Eclipse Foundation, the Content is
+being redistributed by another party ("Redistributor") and different terms and conditions may
+apply to your use of any object code in the Content. Check the Redistributor's license that was
+provided with the Content. If no such license exists, contact the Redistributor. Unless otherwise
+indicated below, the terms and conditions of the EPL still apply to any source code in the Content
+and such source code may be obtained at http://www.eclipse.org.
+
+
+
\ No newline at end of file
diff --git a/third_party/patches/mars/org.eclipse.core.jobs/build.properties b/third_party/patches/mars/org.eclipse.core.jobs/build.properties
new file mode 100644
index 0000000000..87ec7df29b
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.jobs/build.properties
@@ -0,0 +1,20 @@
+###############################################################################
+# Copyright (c) 2005, 2014 IBM Corporation and others.
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Eclipse Public License v1.0
+# which accompanies this distribution, and is available at
+# http://www.eclipse.org/legal/epl-v10.html
+#
+# Contributors:
+# IBM Corporation - initial API and implementation
+###############################################################################
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+ .,\
+ plugin.properties,\
+ .options,\
+ about.html,\
+ plugin.xml
+src.includes = about.html
+jre.compilation.profile = JavaSE-1.7
\ No newline at end of file
diff --git a/third_party/patches/mars/org.eclipse.core.jobs/forceQualifierUpdate.txt b/third_party/patches/mars/org.eclipse.core.jobs/forceQualifierUpdate.txt
new file mode 100644
index 0000000000..56f1032a8a
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.jobs/forceQualifierUpdate.txt
@@ -0,0 +1,2 @@
+# To force a version qualifier update add the bug here
+Bug 403352 - Update all parent versions to match our build stream
diff --git a/third_party/patches/mars/org.eclipse.core.jobs/plugin.properties b/third_party/patches/mars/org.eclipse.core.jobs/plugin.properties
new file mode 100644
index 0000000000..834b35fd15
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.jobs/plugin.properties
@@ -0,0 +1,13 @@
+###############################################################################
+# Copyright (c) 2005, 2014 IBM Corporation and others.
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Eclipse Public License v1.0
+# which accompanies this distribution, and is available at
+# http://www.eclipse.org/legal/epl-v10.html
+#
+# Contributors:
+# IBM Corporation - initial API and implementation
+###############################################################################
+pluginName = Eclipse Jobs Mechanism
+providerName = Eclipse.org
+traceComponentLabel = Eclipse Jobs Mechanism
diff --git a/third_party/patches/mars/org.eclipse.core.jobs/plugin.xml b/third_party/patches/mars/org.eclipse.core.jobs/plugin.xml
new file mode 100644
index 0000000000..60fd36db0e
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.jobs/plugin.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/Counter.java b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/Counter.java
new file mode 100644
index 0000000000..8fb6624be0
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/Counter.java
@@ -0,0 +1,27 @@
+/*******************************************************************************
+ * Copyright (c) 2010, 2012 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.jobs;
+
+/**
+ * Simple thread-safe long counter.
+ * @ThreadSafe
+ */
+public class Counter {
+ private long value = 0L;
+
+ public Counter() {
+ super();
+ }
+
+ public synchronized long increment() {
+ return value++;
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/Deadlock.java b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/Deadlock.java
new file mode 100644
index 0000000000..5b312e6e82
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/Deadlock.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2012 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM - Initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.jobs;
+
+import org.eclipse.core.runtime.jobs.ISchedulingRule;
+
+/**
+ * The deadlock class stores information about a deadlock that just occurred.
+ * It contains an array of the threads that were involved in the deadlock
+ * as well as the thread that was chosen to be suspended and an array of locks
+ * held by that thread that are going to be suspended to resolve the deadlock.
+ */
+class Deadlock {
+ //all the threads which are involved in the deadlock
+ private Thread[] threads;
+ //the thread whose locks will be suspended to resolve deadlock
+ private Thread candidate;
+ //the locks that will be suspended
+ private ISchedulingRule[] locks;
+
+ public Deadlock(Thread[] threads, ISchedulingRule[] locks, Thread candidate) {
+ this.threads = threads;
+ this.locks = locks;
+ this.candidate = candidate;
+ }
+
+ public ISchedulingRule[] getLocks() {
+ return locks;
+ }
+
+ public Thread getCandidate() {
+ return candidate;
+ }
+
+ public Thread[] getThreads() {
+ return threads;
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/DeadlockDetector.java b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/DeadlockDetector.java
new file mode 100644
index 0000000000..13e54eb580
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/DeadlockDetector.java
@@ -0,0 +1,704 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2012 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.jobs;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import org.eclipse.core.internal.runtime.RuntimeLog;
+import org.eclipse.core.runtime.*;
+import org.eclipse.core.runtime.jobs.ILock;
+import org.eclipse.core.runtime.jobs.ISchedulingRule;
+
+/**
+ * Stores all the relationships between locks (rules are also considered locks),
+ * and the threads that own them. All the relationships are stored in a 2D integer array.
+ * The rows in the array are threads, while the columns are locks.
+ * Two corresponding arrayLists store the actual threads and locks.
+ * The index of a thread in the first arrayList is the index of the row in the graph.
+ * The index of a lock in the second arrayList is the index of the column in the graph.
+ * An entry greater than 0 in the graph is the number of times a thread in the entry's row
+ * acquired the lock in the entry's column.
+ * An entry of -1 means that the thread is waiting to acquire the lock.
+ * An entry of 0 means that the thread and the lock have no relationship.
+ *
+ * The difference between rules and locks is that locks can be suspended, while
+ * rules are implicit locks and as such cannot be suspended.
+ * To resolve deadlock, the graph will first try to find a thread that only owns
+ * locks. Failing that, it will find a thread in the deadlock that owns at least
+ * one lock and suspend it.
+ *
+ * Deadlock can only occur among locks, or among locks in combination with rules.
+ * Deadlock among rules only is impossible. Therefore, in any deadlock one can always
+ * find a thread that owns at least one lock that can be suspended.
+ *
+ * The implementation of the graph assumes that a thread can only own 1 rule at
+ * any one time. It can acquire that rule several times, but a thread cannot
+ * acquire 2 non-conflicting rules at the same time.
+ *
+ * The implementation of the graph will sometimes also find and resolve bogus deadlocks.
+ * graph: assuming this rule hierarchy:
+ * R2 R3 L1 R1
+ * J1 1 0 0 / \
+ * J2 0 1 -1 R2 R3
+ * J3 -1 0 1
+ *
+ * If in the above situation job4 decides to acquire rule1, then the graph will transform
+ * to the following:
+ * R2 R3 R1 L1
+ * J1 1 0 1 0
+ * J2 1 1 1 -1
+ * J3 -1 0 0 1
+ * J4 0 0 -1 0
+ *
+ * and the graph will assume that job2 and job3 are deadlocked and suspend lock1 of job3.
+ * The reason the deadlock is bogus is that the deadlock is unlikely to actually happen (the threads
+ * are currently not deadlocked, but might deadlock later on when it is too late to detect it)
+ * Therefore, in order to make sure that no deadlock is possible,
+ * the deadlock will still be resolved at this point.
+ */
+class DeadlockDetector {
+ private static int NO_STATE = 0;
+ //state variables in the graph
+ private static int WAITING_FOR_LOCK = -1;
+ //empty matrix
+ private static final int[][] EMPTY_MATRIX = new int[0][0];
+ //matrix of relationships between threads and locks
+ private int[][] graph = EMPTY_MATRIX;
+ //index is column in adjacency matrix for the lock
+ private final ArrayList locks = new ArrayList();
+ //index is row in adjacency matrix for the thread
+ private final ArrayList lockThreads = new ArrayList();
+ //whether the graph needs to be resized
+ private boolean resize = false;
+
+ /**
+ * Recursively check if any of the threads that prevent the current thread from running
+ * are actually deadlocked with the current thread.
+ * Add the threads that form deadlock to the deadlockedThreads list.
+ */
+ private boolean addCycleThreads(ArrayList deadlockedThreads, Thread next) {
+ //get the thread that block the given thread from running
+ Thread[] blocking = blockingThreads(next);
+ //if the thread is not blocked by other threads, then it is not part of a deadlock
+ if (blocking.length == 0)
+ return false;
+ boolean inCycle = false;
+ for (int i = 0; i < blocking.length; i++) {
+ //if we have already visited the given thread, then we found a cycle
+ if (deadlockedThreads.contains(blocking[i])) {
+ inCycle = true;
+ } else {
+ //otherwise, add the thread to our list and recurse deeper
+ deadlockedThreads.add(blocking[i]);
+ //if the thread is not part of a cycle, remove it from the list
+ if (addCycleThreads(deadlockedThreads, blocking[i]))
+ inCycle = true;
+ else
+ deadlockedThreads.remove(blocking[i]);
+ }
+ }
+ return inCycle;
+ }
+
+ /**
+ * Get the thread(s) that own the lock this thread is waiting for.
+ */
+ private Thread[] blockingThreads(Thread current) {
+ //find the lock this thread is waiting for
+ ISchedulingRule lock = (ISchedulingRule) getWaitingLock(current);
+ return getThreadsOwningLock(lock);
+ }
+
+ /**
+ * Check that the addition of a waiting thread did not produce deadlock.
+ * If deadlock is detected return true, else return false.
+ */
+ private boolean checkWaitCycles(int[] waitingThreads, int lockIndex) {
+ /**
+ * find the lock that this thread is waiting for
+ * recursively check if this is a cycle (i.e. a thread waiting on itself)
+ */
+ for (int i = 0; i < graph.length; i++) {
+ if (graph[i][lockIndex] > NO_STATE) {
+ if (waitingThreads[i] > NO_STATE) {
+ return true;
+ }
+ //keep track that we already visited this thread
+ waitingThreads[i]++;
+ for (int j = 0; j < graph[i].length; j++) {
+ if (graph[i][j] == WAITING_FOR_LOCK) {
+ if (checkWaitCycles(waitingThreads, j))
+ return true;
+ }
+ }
+ //this thread is not involved in a cycle yet, so remove the visited flag
+ waitingThreads[i]--;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns true IFF the matrix contains a row for the given thread.
+ * (meaning the given thread either owns locks or is waiting for locks)
+ */
+ boolean contains(Thread t) {
+ return lockThreads.contains(t);
+ }
+
+ /**
+ * A new rule was just added to the graph.
+ * Find a rule it conflicts with and update the new rule with the number of times
+ * it was acquired implicitly when threads acquired conflicting rule.
+ */
+ private void fillPresentEntries(ISchedulingRule newLock, int lockIndex) {
+ //fill in the entries for the new rule from rules it conflicts with
+ for (int j = 0; j < locks.size(); j++) {
+ if ((j != lockIndex) && (newLock.isConflicting(locks.get(j)))) {
+ for (int i = 0; i < graph.length; i++) {
+ if ((graph[i][j] > NO_STATE) && (graph[i][lockIndex] == NO_STATE))
+ graph[i][lockIndex] = graph[i][j];
+ }
+ }
+ }
+ //now back fill the entries for rules the current rule conflicts with
+ for (int j = 0; j < locks.size(); j++) {
+ if ((j != lockIndex) && (newLock.isConflicting(locks.get(j)))) {
+ for (int i = 0; i < graph.length; i++) {
+ if ((graph[i][lockIndex] > NO_STATE) && (graph[i][j] == NO_STATE))
+ graph[i][j] = graph[i][lockIndex];
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns all the locks owned by the given thread
+ */
+ private Object[] getOwnedLocks(Thread current) {
+ ArrayList ownedLocks = new ArrayList(1);
+ int index = indexOf(current, false);
+
+ for (int j = 0; j < graph[index].length; j++) {
+ if (graph[index][j] > NO_STATE)
+ ownedLocks.add(locks.get(j));
+ }
+ if (ownedLocks.size() == 0)
+ Assert.isLegal(false, "A thread with no locks is part of a deadlock."); //$NON-NLS-1$
+ return ownedLocks.toArray();
+ }
+
+ /**
+ * Returns an array of threads that form the deadlock (usually 2).
+ */
+ private Thread[] getThreadsInDeadlock(Thread cause) {
+ ArrayList deadlockedThreads = new ArrayList(2);
+ /**
+ * if the thread that caused deadlock doesn't own any locks, then it is not part
+ * of the deadlock (it just caused it because of a rule it tried to acquire)
+ */
+ if (ownsLocks(cause))
+ deadlockedThreads.add(cause);
+ addCycleThreads(deadlockedThreads, cause);
+ return deadlockedThreads.toArray(new Thread[deadlockedThreads.size()]);
+ }
+
+ /**
+ * Returns the thread(s) that own the given lock.
+ */
+ private Thread[] getThreadsOwningLock(ISchedulingRule rule) {
+ if (rule == null)
+ return new Thread[0];
+ int lockIndex = indexOf(rule, false);
+ ArrayList blocking = new ArrayList(1);
+ for (int i = 0; i < graph.length; i++) {
+ if (graph[i][lockIndex] > NO_STATE)
+ blocking.add(lockThreads.get(i));
+ }
+ if ((blocking.size() == 0) && (JobManager.DEBUG_LOCKS))
+ System.out.println("Lock " + rule + " is involved in deadlock but is not owned by any thread."); //$NON-NLS-1$ //$NON-NLS-2$
+ if ((blocking.size() > 1) && (rule instanceof ILock) && (JobManager.DEBUG_LOCKS))
+ System.out.println("Lock " + rule + " is owned by more than 1 thread, but it is not a rule."); //$NON-NLS-1$ //$NON-NLS-2$
+ return blocking.toArray(new Thread[blocking.size()]);
+ }
+
+ /**
+ * Returns the lock the given thread is waiting for.
+ */
+ private Object getWaitingLock(Thread current) {
+ int index = indexOf(current, false);
+ //find the lock that this thread is waiting for
+ for (int j = 0; j < graph[index].length; j++) {
+ if (graph[index][j] == WAITING_FOR_LOCK)
+ return locks.get(j);
+ }
+ //it can happen that a thread is not waiting for any lock (it is not really part of the deadlock)
+ return null;
+ }
+
+ /**
+ * Returns the index of the given lock in the lock array. If the lock is
+ * not present in the array, it is added to the end.
+ */
+ private int indexOf(ISchedulingRule lock, boolean add) {
+ int index = locks.indexOf(lock);
+ if ((index < 0) && add) {
+ locks.add(lock);
+ resize = true;
+ index = locks.size() - 1;
+ }
+ return index;
+ }
+
+ /**
+ * Returns the index of the given thread in the thread array. If the thread
+ * is not present in the array, it is added to the end.
+ */
+ private int indexOf(Thread owner, boolean add) {
+ int index = lockThreads.indexOf(owner);
+ if ((index < 0) && add) {
+ lockThreads.add(owner);
+ resize = true;
+ index = lockThreads.size() - 1;
+ }
+ return index;
+ }
+
+ /**
+ * Returns true IFF the adjacency matrix is empty.
+ */
+ boolean isEmpty() {
+ return (locks.size() == 0) && (lockThreads.size() == 0) && (graph.length == 0);
+ }
+
+ /**
+ * The given lock was acquired by the given thread.
+ */
+ void lockAcquired(Thread owner, ISchedulingRule lock) {
+ int lockIndex = indexOf(lock, true);
+ int threadIndex = indexOf(owner, true);
+ if (resize)
+ resizeGraph();
+ if (graph[threadIndex][lockIndex] == WAITING_FOR_LOCK)
+ graph[threadIndex][lockIndex] = NO_STATE;
+ /**
+ * acquire all locks that conflict with the given lock
+ * or conflict with a lock the given lock will acquire implicitly
+ * (locks are acquired implicitly when a conflicting lock is acquired)
+ */
+ ArrayList conflicting = new ArrayList(1);
+ //only need two passes through all the locks to pick up all conflicting rules
+ int NUM_PASSES = 2;
+ conflicting.add(lock);
+ graph[threadIndex][lockIndex]++;
+ for (int i = 0; i < NUM_PASSES; i++) {
+ for (int k = 0; k < conflicting.size(); k++) {
+ ISchedulingRule current = conflicting.get(k);
+ for (int j = 0; j < locks.size(); j++) {
+ ISchedulingRule possible = locks.get(j);
+ if (current.isConflicting(possible) && !conflicting.contains(possible)) {
+ conflicting.add(possible);
+ graph[threadIndex][j]++;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * The given lock was released by the given thread. Update the graph.
+ */
+ void lockReleased(Thread owner, ISchedulingRule lock) {
+ int lockIndex = indexOf(lock, false);
+ int threadIndex = indexOf(owner, false);
+ //make sure the lock and thread exist in the graph
+ if (threadIndex < 0) {
+ if (JobManager.DEBUG_LOCKS)
+ System.out.println("[lockReleased] Lock " + lock + " was already released by thread " + owner.getName()); //$NON-NLS-1$ //$NON-NLS-2$
+ return;
+ }
+ if (lockIndex < 0) {
+ if (JobManager.DEBUG_LOCKS)
+ System.out.println("[lockReleased] Thread " + owner.getName() + " already released lock " + lock); //$NON-NLS-1$ //$NON-NLS-2$
+ return;
+ }
+ //if this lock was suspended, set it to NO_STATE
+ if ((lock instanceof ILock) && (graph[threadIndex][lockIndex] == WAITING_FOR_LOCK)) {
+ graph[threadIndex][lockIndex] = NO_STATE;
+ return;
+ }
+ //release all locks that conflict with the given lock
+ //or release all rules that are owned by the given thread, if we are releasing a rule
+ for (int j = 0; j < graph[threadIndex].length; j++) {
+ if ((lock.isConflicting(locks.get(j))) || (!(lock instanceof ILock) && !(locks.get(j) instanceof ILock) && (graph[threadIndex][j] > NO_STATE))) {
+ if (graph[threadIndex][j] == NO_STATE) {
+ if (JobManager.DEBUG_LOCKS)
+ System.out.println("[lockReleased] More releases than acquires for thread " + owner.getName() + " and lock " + lock); //$NON-NLS-1$ //$NON-NLS-2$
+ } else {
+ graph[threadIndex][j]--;
+ }
+ }
+ }
+ //if this thread just released the given lock, try to simplify the graph
+ if (graph[threadIndex][lockIndex] == NO_STATE)
+ reduceGraph(threadIndex, lock);
+ }
+
+ /**
+ * The given scheduling rule is no longer used because the job that invoked it is done.
+ * Release this rule regardless of how many times it was acquired.
+ */
+ void lockReleasedCompletely(Thread owner, ISchedulingRule rule) {
+ int ruleIndex = indexOf(rule, false);
+ int threadIndex = indexOf(owner, false);
+ //need to make sure that the given thread and rule were not already removed from the graph
+ if (threadIndex < 0) {
+ if (JobManager.DEBUG_LOCKS)
+ System.out.println("[lockReleasedCompletely] Lock " + rule + " was already released by thread " + owner.getName()); //$NON-NLS-1$ //$NON-NLS-2$
+ return;
+ }
+ if (ruleIndex < 0) {
+ if (JobManager.DEBUG_LOCKS)
+ System.out.println("[lockReleasedCompletely] Thread " + owner.getName() + " already released lock " + rule); //$NON-NLS-1$ //$NON-NLS-2$
+ return;
+ }
+ /**
+ * set all rules that are owned by the given thread to NO_STATE
+ * (not just rules that conflict with the rule we are releasing)
+ * if we are releasing a lock, then only update the one entry for the lock
+ */
+ for (int j = 0; j < graph[threadIndex].length; j++) {
+ if (!(locks.get(j) instanceof ILock) && (graph[threadIndex][j] > NO_STATE))
+ graph[threadIndex][j] = NO_STATE;
+ }
+ reduceGraph(threadIndex, rule);
+ }
+
+ /**
+ * The given thread could not get the given lock and is waiting for it.
+ * Update the graph.
+ */
+ Deadlock lockWaitStart(Thread client, ISchedulingRule lock) {
+ setToWait(client, lock, false);
+ int lockIndex = indexOf(lock, false);
+ int[] temp = new int[lockThreads.size()];
+ //check if the addition of the waiting thread caused deadlock
+ if (!checkWaitCycles(temp, lockIndex))
+ return null;
+ //there is a deadlock in the graph
+ Thread[] threads = getThreadsInDeadlock(client);
+ //find a thread whose locks can be suspended to resolve the deadlock
+ Thread candidate = resolutionCandidate(threads);
+ ISchedulingRule[] locksToSuspend = realLocksForThread(candidate);
+ Deadlock deadlock = new Deadlock(threads, locksToSuspend, candidate);
+ reportDeadlock(deadlock);
+ if (JobManager.DEBUG_DEADLOCK)
+ throw new IllegalStateException("Deadlock detected. Caused by thread " + client.getName() + '.'); //$NON-NLS-1$
+ // Update the graph to indicate that the locks will now be suspended.
+ // To indicate that the lock will be suspended, we set the thread to wait for the lock.
+ // When the lock is forced to be released, the entry will be cleared.
+ for (int i = 0; i < locksToSuspend.length; i++)
+ setToWait(deadlock.getCandidate(), locksToSuspend[i], true);
+ return deadlock;
+ }
+
+ /**
+ * The given thread has stopped waiting for the given lock.
+ * Update the graph.
+ * If the lock has already been granted, then it isn't removed.
+ */
+ void lockWaitStop(Thread owner, ISchedulingRule lock) {
+ int lockIndex = indexOf(lock, false);
+ int threadIndex = indexOf(owner, false);
+ //make sure the thread and lock exist in the graph
+ if (threadIndex < 0) {
+ if (JobManager.DEBUG_LOCKS)
+ System.out.println("Thread " + owner.getName() + " was already removed."); //$NON-NLS-1$ //$NON-NLS-2$
+ return;
+ }
+ if (lockIndex < 0) {
+ if (JobManager.DEBUG_LOCKS)
+ System.out.println("Lock " + lock + " was already removed."); //$NON-NLS-1$ //$NON-NLS-2$
+ return;
+ }
+ if (graph[threadIndex][lockIndex] != WAITING_FOR_LOCK) {
+ // Lock has already been granted, nothing to do...
+ if (JobManager.DEBUG_LOCKS)
+ System.out.println("Lock " + lock + " already granted to depth: " + graph[threadIndex][lockIndex]); //$NON-NLS-1$ //$NON-NLS-2$
+ return;
+ }
+ graph[threadIndex][lockIndex] = NO_STATE;
+ reduceGraph(threadIndex, lock);
+ }
+
+ /**
+ * Returns true IFF the given thread owns a single lock
+ */
+ private boolean ownsLocks(Thread cause) {
+ int threadIndex = indexOf(cause, false);
+ for (int j = 0; j < graph[threadIndex].length; j++) {
+ if (graph[threadIndex][j] > NO_STATE)
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns true IFF the given thread owns a single real lock.
+ * A real lock is a lock that can be suspended.
+ */
+ private boolean ownsRealLocks(Thread owner) {
+ int threadIndex = indexOf(owner, false);
+ for (int j = 0; j < graph[threadIndex].length; j++) {
+ if (graph[threadIndex][j] > NO_STATE) {
+ Object lock = locks.get(j);
+ if (lock instanceof ILock)
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Return true IFF this thread owns rule locks (i.e. implicit locks which
+ * cannot be suspended)
+ */
+ private boolean ownsRuleLocks(Thread owner) {
+ int threadIndex = indexOf(owner, false);
+ for (int j = 0; j < graph[threadIndex].length; j++) {
+ if (graph[threadIndex][j] > NO_STATE) {
+ Object lock = locks.get(j);
+ if (!(lock instanceof ILock))
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns an array of real locks that are owned by the given thread.
+ * Real locks are locks that implement the ILock interface and can be suspended.
+ */
+ private ISchedulingRule[] realLocksForThread(Thread owner) {
+ int threadIndex = indexOf(owner, false);
+ ArrayList ownedLocks = new ArrayList(1);
+ for (int j = 0; j < graph[threadIndex].length; j++) {
+ if ((graph[threadIndex][j] > NO_STATE) && (locks.get(j) instanceof ILock))
+ ownedLocks.add(locks.get(j));
+ }
+ if (ownedLocks.size() == 0)
+ Assert.isLegal(false, "A thread with no real locks was chosen to resolve deadlock."); //$NON-NLS-1$
+ return ownedLocks.toArray(new ISchedulingRule[ownedLocks.size()]);
+ }
+
+ /**
+ * The matrix has been simplified. Check if any unnecessary rows or columns
+ * can be removed.
+ */
+ private void reduceGraph(int row, ISchedulingRule lock) {
+ int numLocks = locks.size();
+ boolean[] emptyColumns = new boolean[numLocks];
+
+ /**
+ * find all columns that could possibly be empty
+ * (consist of locks which conflict with the given lock, or of locks which are rules)
+ */
+ for (int j = 0; j < numLocks; j++) {
+ if ((lock.isConflicting(locks.get(j))) || !(locks.get(j) instanceof ILock))
+ emptyColumns[j] = true;
+ }
+
+ boolean rowEmpty = true;
+ int numEmpty = 0;
+ //check if the given row is empty
+ for (int j = 0; j < graph[row].length; j++) {
+ if (graph[row][j] != NO_STATE) {
+ rowEmpty = false;
+ break;
+ }
+ }
+ /**
+ * Check if the possibly empty columns are actually empty.
+ * If a column is actually empty, remove the corresponding lock from the list of locks
+ * Start at the last column so that when locks are removed from the list,
+ * the index of the remaining locks is unchanged. Store the number of empty columns.
+ */
+ for (int j = emptyColumns.length - 1; j >= 0; j--) {
+ for (int i = 0; i < graph.length; i++) {
+ if (emptyColumns[j] && (graph[i][j] != NO_STATE)) {
+ emptyColumns[j] = false;
+ break;
+ }
+ }
+ if (emptyColumns[j]) {
+ locks.remove(j);
+ numEmpty++;
+ }
+ }
+ //if no columns or rows are empty, return right away
+ if ((numEmpty == 0) && (!rowEmpty))
+ return;
+
+ if (rowEmpty)
+ lockThreads.remove(row);
+
+ //new graph (the list of locks and the list of threads are already updated)
+ final int numThreads = lockThreads.size();
+ numLocks = locks.size();
+ //optimize empty graph case
+ if (numThreads == 0 && numLocks == 0) {
+ graph = EMPTY_MATRIX;
+ return;
+ }
+ int[][] tempGraph = new int[numThreads][numLocks];
+
+ //the number of rows we need to skip to get the correct entry from the old graph
+ int numRowsSkipped = 0;
+ for (int i = 0; i < graph.length - numRowsSkipped; i++) {
+ if ((i == row) && rowEmpty) {
+ numRowsSkipped++;
+ //check if we need to skip the last row
+ if (i >= graph.length - numRowsSkipped)
+ break;
+ }
+ //the number of columns we need to skip to get the correct entry from the old graph
+ //needs to be reset for every new row
+ int numColsSkipped = 0;
+ for (int j = 0; j < graph[i].length - numColsSkipped; j++) {
+ while (emptyColumns[j + numColsSkipped]) {
+ numColsSkipped++;
+ //check if we need to skip the last column
+ if (j >= graph[i].length - numColsSkipped)
+ break;
+ }
+ //need to break out of the outer loop
+ if (j >= graph[i].length - numColsSkipped)
+ break;
+ tempGraph[i][j] = graph[i + numRowsSkipped][j + numColsSkipped];
+ }
+ }
+ graph = tempGraph;
+ Assert.isTrue(numThreads == graph.length, "Rows and threads don't match."); //$NON-NLS-1$
+ Assert.isTrue(numLocks == ((graph.length > 0) ? graph[0].length : 0), "Columns and locks don't match."); //$NON-NLS-1$
+ }
+
+ /**
+ * Adds a 'deadlock detected' message to the log with a stack trace.
+ */
+ private void reportDeadlock(Deadlock deadlock) {
+ String msg = "Deadlock detected. All locks owned by thread " + deadlock.getCandidate().getName() + " will be suspended."; //$NON-NLS-1$ //$NON-NLS-2$
+ MultiStatus main = new MultiStatus(JobManager.PI_JOBS, JobManager.PLUGIN_ERROR, msg, new IllegalStateException());
+ Thread[] threads = deadlock.getThreads();
+ for (int i = 0; i < threads.length; i++) {
+ Object[] ownedLocks = getOwnedLocks(threads[i]);
+ Object waitLock = getWaitingLock(threads[i]);
+ StringBuffer buf = new StringBuffer("Thread "); //$NON-NLS-1$
+ buf.append(threads[i].getName());
+ buf.append(" has locks: "); //$NON-NLS-1$
+ for (int j = 0; j < ownedLocks.length; j++) {
+ buf.append(ownedLocks[j]);
+ buf.append((j < ownedLocks.length - 1) ? ", " : " "); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ buf.append("and is waiting for lock "); //$NON-NLS-1$
+ buf.append(waitLock);
+ Status child = new Status(IStatus.ERROR, JobManager.PI_JOBS, JobManager.PLUGIN_ERROR, buf.toString(), null);
+ main.add(child);
+ }
+ RuntimeLog.log(main);
+ }
+
+ /**
+ * The number of threads/locks in the graph has changed. Update the
+ * underlying matrix.
+ */
+ private void resizeGraph() {
+ // a new row and/or a new column was added to the graph.
+ // since new rows/columns are always added to the end, just transfer
+ // old entries to the new graph, with the same indices.
+ final int newRows = lockThreads.size();
+ final int newCols = locks.size();
+ //optimize 0x0 and 1x1 matrices
+ if (newRows == 0 && newCols == 0) {
+ graph = EMPTY_MATRIX;
+ return;
+ }
+ int[][] tempGraph = new int[newRows][newCols];
+ for (int i = 0; i < graph.length; i++)
+ System.arraycopy(graph[i], 0, tempGraph[i], 0, graph[i].length);
+ graph = tempGraph;
+ resize = false;
+ }
+
+ /**
+ * Get the thread whose locks can be suspended. (i.e. all locks it owns are
+ * actual locks and not rules). Return the first thread in the array by default.
+ */
+ private Thread resolutionCandidate(Thread[] candidates) {
+ //first look for a candidate that has no scheduling rules
+ for (int i = 0; i < candidates.length; i++) {
+ if (!ownsRuleLocks(candidates[i]))
+ return candidates[i];
+ }
+ //next look for any candidate with a real lock (a lock that can be suspended)
+ for (int i = 0; i < candidates.length; i++) {
+ if (ownsRealLocks(candidates[i]))
+ return candidates[i];
+ }
+ //unnecessary, return the first entry in the array by default
+ return candidates[0];
+ }
+
+ /**
+ * The given thread is waiting for the given lock. Update the graph.
+ */
+ private void setToWait(Thread owner, ISchedulingRule lock, boolean suspend) {
+ boolean needTransfer = false;
+ /**
+ * if we are adding an entry where a thread is waiting on a scheduling rule,
+ * then we need to transfer all positive entries for a conflicting rule to the
+ * newly added rule in order to synchronize the graph.
+ */
+ if (!suspend && !(lock instanceof ILock))
+ needTransfer = true;
+ int lockIndex = indexOf(lock, !suspend);
+ int threadIndex = indexOf(owner, !suspend);
+ if (resize)
+ resizeGraph();
+
+ graph[threadIndex][lockIndex] = WAITING_FOR_LOCK;
+ if (needTransfer)
+ fillPresentEntries(lock, lockIndex);
+ }
+
+ /**
+ * Prints out the current matrix to standard output.
+ * Only used for debugging.
+ */
+ public String toDebugString() {
+ StringWriter sWriter = new StringWriter();
+ PrintWriter out = new PrintWriter(sWriter, true);
+ out.println(" :: "); //$NON-NLS-1$
+ for (int j = 0; j < locks.size(); j++) {
+ out.print(" " + locks.get(j) + ','); //$NON-NLS-1$
+ }
+ out.println();
+ for (int i = 0; i < graph.length; i++) {
+ out.print(" " + lockThreads.get(i).getName() + " : "); //$NON-NLS-1$ //$NON-NLS-2$
+ for (int j = 0; j < graph[i].length; j++) {
+ out.print(" " + graph[i][j] + ','); //$NON-NLS-1$
+ }
+ out.println();
+ }
+ out.println("-------"); //$NON-NLS-1$
+ return sWriter.toString();
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/ImplicitJobs.java b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/ImplicitJobs.java
new file mode 100644
index 0000000000..6e081aeba6
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/ImplicitJobs.java
@@ -0,0 +1,298 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2012 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM - Initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.jobs;
+
+import java.util.*;
+import org.eclipse.core.internal.runtime.RuntimeLog;
+import org.eclipse.core.runtime.*;
+import org.eclipse.core.runtime.jobs.ISchedulingRule;
+import org.eclipse.core.runtime.jobs.Job;
+
+/**
+ * Implicit jobs are jobs that are running by virtue of a JobManager.begin/end
+ * pair. They act like normal jobs, except they are tied to an arbitrary thread
+ * of the client's choosing, and they can be nested.
+ * @ThreadSafe
+ */
+class ImplicitJobs {
+
+ /**
+ * Cached unused instance that can be reused
+ * @GuardedBy("this")
+ */
+ private ThreadJob jobCache = null;
+ protected JobManager manager;
+
+ /**
+ * Set of suspended scheduling rules.
+ * @GuardedBy("this")
+ */
+ private final Set suspendedRules = new HashSet(20);
+
+ /**
+ * Maps (Thread->ThreadJob), threads to the currently running job for that
+ * thread.
+ * @GuardedBy("this")
+ */
+ private final Map threadJobs = new HashMap(20);
+
+ ImplicitJobs(JobManager manager) {
+ this.manager = manager;
+ }
+
+ /* (Non-javadoc)
+ * @see IJobManager#beginRule
+ */
+ void begin(ISchedulingRule rule, IProgressMonitor monitor, boolean suspend) {
+ if (JobManager.DEBUG_BEGIN_END)
+ JobManager.debug("Begin rule: " + rule); //$NON-NLS-1$
+ final Thread currentThread = Thread.currentThread();
+ ThreadJob threadJob;
+ synchronized (this) {
+ threadJob = threadJobs.get(currentThread);
+ if (threadJob != null) {
+ //nested rule, just push on stack and return
+ threadJob.push(rule);
+ return;
+ }
+ //no need to schedule a thread job for a null rule
+ if (rule == null)
+ return;
+ //create a thread job for this thread, use the rule from the real job if it has one
+ Job realJob = manager.currentJob();
+ if (realJob != null && realJob.getRule() != null)
+ threadJob = newThreadJob(realJob.getRule());
+ else {
+ threadJob = newThreadJob(rule);
+ threadJob.acquireRule = true;
+ }
+ //don't acquire rule if it is a suspended rule
+ if (isSuspended(rule))
+ threadJob.acquireRule = false;
+ //indicate if it is a system job to ensure isBlocking works correctly
+ threadJob.setRealJob(realJob);
+ threadJob.setThread(currentThread);
+ }
+ try {
+ threadJob.push(rule);
+ //join the thread job outside sync block
+ if (threadJob.acquireRule) {
+ //no need to re-acquire any locks because the thread did not wait to get this lock
+ if (manager.runNow(threadJob, false) == null)
+ manager.getLockManager().addLockThread(Thread.currentThread(), rule);
+ else
+ threadJob = ThreadJob.joinRun(threadJob, monitor);
+ }
+ } finally {
+ //remember this thread job - only do this
+ //after the rule is acquired because it is ok for this thread to acquire
+ //and release other rules while waiting.
+ synchronized (this) {
+ threadJobs.put(currentThread, threadJob);
+ if (suspend)
+ suspendedRules.add(rule);
+ }
+ }
+ }
+
+ /* (Non-javadoc)
+ * @see IJobManager#endRule
+ */
+ synchronized void end(ISchedulingRule rule, boolean resume) {
+ if (JobManager.DEBUG_BEGIN_END)
+ JobManager.debug("End rule: " + rule); //$NON-NLS-1$
+ ThreadJob threadJob = threadJobs.get(Thread.currentThread());
+ if (threadJob == null)
+ Assert.isLegal(rule == null, "endRule without matching beginRule: " + rule); //$NON-NLS-1$
+ else if (threadJob.pop(rule)) {
+ endThreadJob(threadJob, resume);
+ }
+ }
+
+ /**
+ * Called when a worker thread has finished running a job. At this
+ * point, the worker thread must not own any scheduling rules
+ * @param lastJob The last job to run in this thread
+ */
+ void endJob(InternalJob lastJob) {
+ final Thread currentThread = Thread.currentThread();
+ IStatus error;
+ synchronized (this) {
+ ThreadJob threadJob = threadJobs.get(currentThread);
+ if (threadJob == null) {
+ if (lastJob.getRule() != null)
+ notifyWaitingThreadJobs(lastJob);
+ return;
+ }
+ String msg = "Worker thread ended job: " + lastJob + ", but still holds rule: " + threadJob; //$NON-NLS-1$ //$NON-NLS-2$
+ error = new Status(IStatus.ERROR, JobManager.PI_JOBS, 1, msg, null);
+ //end the thread job
+ endThreadJob(threadJob, false);
+ }
+ try {
+ RuntimeLog.log(error);
+ } catch (RuntimeException e) {
+ //failed to log, so print to console instead
+ System.err.println(error.getMessage());
+ }
+ }
+
+ /**
+ * @GuardedBy("this")
+ */
+ private void endThreadJob(ThreadJob threadJob, boolean resume) {
+ Thread currentThread = Thread.currentThread();
+ //clean up when last rule scope exits
+ threadJobs.remove(currentThread);
+ ISchedulingRule rule = threadJob.getRule();
+ if (resume && rule != null)
+ suspendedRules.remove(rule);
+ //if this job had a rule, then we are essentially releasing a lock
+ //note it is safe to do this even if the acquire was aborted
+ if (threadJob.acquireRule) {
+ manager.getLockManager().removeLockThread(currentThread, rule);
+ notifyWaitingThreadJobs(threadJob);
+ }
+ //if the job was started, we need to notify job manager to end it
+ if (threadJob.isRunning())
+ manager.endJob(threadJob, Status.OK_STATUS, false);
+ recycle(threadJob);
+ }
+
+ /**
+ * Returns true if this rule has been suspended, and false otherwise.
+ * @GuardedBy("this")
+ */
+ private boolean isSuspended(ISchedulingRule rule) {
+ if (suspendedRules.size() == 0)
+ return false;
+ for (Iterator it = suspendedRules.iterator(); it.hasNext();)
+ if (it.next().contains(rule))
+ return true;
+ return false;
+ }
+
+ /**
+ * Returns a new or reused ThreadJob instance.
+ * @GuardedBy("this")
+ */
+ private ThreadJob newThreadJob(ISchedulingRule rule) {
+ if (jobCache != null) {
+ ThreadJob job = jobCache;
+ // calling setRule will try to acquire JobManager.lock, breaking
+ // lock acquisition protocol. Since we managing this special job
+ // ourselves we can call internalSetRule
+ ((InternalJob) job).internalSetRule(rule);
+ job.acquireRule = job.isRunning = false;
+ job.realJob = null;
+ jobCache = null;
+ return job;
+ }
+ return new ThreadJob(rule);
+ }
+
+ /**
+ * A job has just finished that was holding a scheduling rule, and the
+ * scheduling rule is now free. Wake any blocked thread jobs so they can
+ * compete for the newly freed lock
+ */
+ void notifyWaitingThreadJobs(InternalJob job) {
+ synchronized (job.jobStateLock) {
+ job.jobStateLock.notifyAll();
+ }
+ }
+
+ /**
+ * Indicates that a thread job is no longer in use and can be reused.
+ * @GuardedBy("this")
+ */
+ private void recycle(ThreadJob job) {
+ if (jobCache == null && job.recycle())
+ jobCache = job;
+ }
+
+ /**
+ * Implements IJobManager#resume(ISchedulingRule)
+ * @param rule
+ */
+ void resume(ISchedulingRule rule) {
+ //resume happens as a consequence of freeing the last rule in the stack
+ end(rule, true);
+ if (JobManager.DEBUG_BEGIN_END)
+ JobManager.debug("Resume rule: " + rule); //$NON-NLS-1$
+ }
+
+ /**
+ * Implements IJobManager#suspend(ISchedulingRule, IProgressMonitor)
+ * @param rule
+ * @param monitor
+ */
+ void suspend(ISchedulingRule rule, IProgressMonitor monitor) {
+ if (JobManager.DEBUG_BEGIN_END)
+ JobManager.debug("Suspend rule: " + rule); //$NON-NLS-1$
+ //the suspend job will be remembered once the rule is acquired
+ begin(rule, monitor, true);
+ }
+
+ /**
+ * Implements IJobManager#transferRule(ISchedulingRule, Thread)
+ */
+ synchronized void transfer(ISchedulingRule rule, Thread destinationThread) {
+ //nothing to do for null
+ if (rule == null)
+ return;
+ final Thread currentThread = Thread.currentThread();
+ //nothing to do if transferring to the same thread
+ if (currentThread == destinationThread)
+ return;
+ //ensure destination thread doesn't already have a rule
+ ThreadJob target = threadJobs.get(destinationThread);
+ Assert.isLegal(target == null, "Transfer rule to job that already owns a rule"); //$NON-NLS-1$
+ //ensure calling thread owns the job being transferred
+ ThreadJob source = threadJobs.get(currentThread);
+ Assert.isNotNull(source, "transferRule without beginRule"); //$NON-NLS-1$
+ Assert.isLegal(source.getRule() == rule, "transferred rule " + rule + " does not match beginRule: " + source.getRule()); //$NON-NLS-1$ //$NON-NLS-2$ // transfer the thread job without ending it
+ source.setThread(destinationThread);
+ threadJobs.remove(currentThread);
+ threadJobs.put(destinationThread, source);
+ // transfer lock
+ if (source.acquireRule) {
+ manager.getLockManager().removeLockThread(currentThread, rule);
+ manager.getLockManager().addLockThread(destinationThread, rule);
+ }
+ // Wake up any blocked jobs (waiting within yield or joinRun) waiting on
+ // this rule
+ notifyWaitingThreadJobs(source);
+ }
+
+ synchronized void removeWaiting(ThreadJob threadJob) {
+ synchronized (((InternalJob) threadJob).jobStateLock) {
+ threadJob.isWaiting = false;
+ notifyWaitingThreadJobs(threadJob);
+ ((InternalJob) threadJob).setWaitQueueStamp(InternalJob.T_NONE);
+ }
+ manager.dequeue(manager.waitingThreadJobs, threadJob);
+ }
+
+ synchronized void addWaiting(ThreadJob threadJob) {
+ synchronized (((InternalJob) threadJob).jobStateLock) {
+ threadJob.isWaiting = true;
+ notifyWaitingThreadJobs(threadJob);
+ ((InternalJob) threadJob).setWaitQueueStamp(manager.waitQueueCounter.increment());
+ }
+ manager.enqueue(manager.waitingThreadJobs, threadJob);
+ }
+
+ synchronized ThreadJob getThreadJob(Thread thread) {
+ return threadJobs.get(thread);
+ }
+
+}
diff --git a/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/InternalJob.java b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/InternalJob.java
new file mode 100644
index 0000000000..d0eebb9567
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/InternalJob.java
@@ -0,0 +1,554 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2014 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM - Initial API and implementation
+ * Stephan Wahlbrink - Fix for bug 200997.
+ * Thirumala Reddy Mutchukota (thirumala@google.com) -
+ * Bug 432049, JobGroup API and implementation
+ * Bug 105821, Support for Job#join with timeout and progress monitor
+ *******************************************************************************/
+package org.eclipse.core.internal.jobs;
+
+import java.util.Map;
+import org.eclipse.core.runtime.*;
+import org.eclipse.core.runtime.jobs.*;
+
+/**
+ * Internal implementation class for jobs. Clients must not implement this class
+ * directly. All jobs must be subclasses of the API org.eclipse.core.runtime.jobs.Job class.
+ */
+public abstract class InternalJob extends PlatformObject implements Comparable {
+ /**
+ * Job state code (value 16) indicating that a job has been removed from
+ * the wait queue and is about to start running. From an API point of view,
+ * this is the same as RUNNING.
+ */
+ static final int ABOUT_TO_RUN = 0x10;
+
+ /**
+ * Job state code (value 32) indicating that a job has passed scheduling
+ * precondition checks and is about to be added to the wait queue. From an API point of view,
+ * this is the same as WAITING.
+ */
+ static final int ABOUT_TO_SCHEDULE = 0x20;
+ /**
+ * Job state code (value 8) indicating that a job is blocked by another currently
+ * running job. From an API point of view, this is the same as WAITING.
+ */
+ static final int BLOCKED = 0x08;
+ /**
+ * Job state code (value 64) indicating that a job is yielding.
+ * From an API point of view, this is the same as WAITING.
+ */
+ static final int YIELDING = 0x40;
+
+ //flag mask bits
+ private static final int M_STATE = 0xFF;
+ private static final int M_SYSTEM = 0x0100;
+ private static final int M_USER = 0x0200;
+
+ /*
+ * flag on a job indicating that it was about to run, but has been canceled
+ */
+ private static final int M_ABOUT_TO_RUN_CANCELED = 0x0400;
+
+ /*
+ * Flag on a job indicating that it was canceled when running. This flag
+ * is used to ensure that #canceling is only ever called once on a job in
+ * case of recursive cancelation attempts.
+ */
+ private static final int M_RUN_CANCELED = 0x0800;
+
+ private static int nextJobNumber = 0;
+ protected static final JobManager manager = JobManager.getInstance();
+
+ /**
+ * Start time constant indicating a job should be started at
+ * a time in the infinite future, causing it to sleep forever.
+ */
+ static final long T_INFINITE = Long.MAX_VALUE;
+ /**
+ * Start time constant indicating that the job has no start time.
+ */
+ static final long T_NONE = -1;
+
+ private volatile int flags = Job.NONE;
+ private final int jobNumber = getNextJobNumber();
+ /**
+ * The list of job listeners. Never null.
+ * @GuardedBy("itself")
+ */
+ private final ListenerList listeners = new ListenerList(ListenerList.IDENTITY);
+
+ private volatile IProgressMonitor monitor;
+ private String name;
+ private JobGroup jobGroup;
+ /**
+ * The job ahead of me in a queue or list.
+ * @GuardedBy("manager.lock")
+ */
+ private InternalJob next;
+ /**
+ * The job behind me in a queue or list.
+ * @GuardedBy("manager.lock")
+ */
+ private InternalJob previous;
+ private int priority = Job.LONG;
+ /**
+ * Arbitrary properties (key,value) pairs, attached
+ * to a job instance by a third party.
+ */
+ private ObjectMap properties;
+
+ /**
+ * Volatile because it is usually set via a Worker thread and is read via a
+ * client thread.
+ */
+ private volatile IStatus result;
+ /**
+ * @GuardedBy("manager.lock")
+ */
+ private ISchedulingRule schedulingRule;
+ /**
+ * If the job is waiting, this represents the time the job should start by.
+ * If this job is sleeping, this represents the time the job should wake up.
+ * If this job is running, this represents the delay automatic rescheduling,
+ * or -1 if the job should not be rescheduled.
+ * @GuardedBy("manager.lock")
+ */
+ private long startTime;
+
+ /**
+ * Stamp added when a job is added to the wait queue. Used to ensure
+ * jobs in the wait queue maintain their insertion order even if they are
+ * removed from the wait queue temporarily while blocked
+ * @GuardedBy("manager.lock")
+ */
+ private long waitQueueStamp = T_NONE;
+
+ /*
+ * The thread that is currently running this job
+ */
+ private volatile Thread thread = null;
+
+ /**
+ * This lock will be held while performing state changes on this job. It is
+ * also used as a notifier used to wake up yielding jobs or waiting ThreadJobs
+ * when 1) a conflicting job completes and releases a scheduling rule, or 2)
+ * when a this job changes state.
+ *
+ * See also the lock ordering protocol explanation in JobManager's
+ * documentation.
+ *
+ * @GuardedBy("itself")
+ */
+ final Object jobStateLock = new Object();
+
+ private static synchronized int getNextJobNumber() {
+ return nextJobNumber++;
+ }
+
+ protected InternalJob(String name) {
+ Assert.isNotNull(name);
+ this.name = name;
+ }
+
+ protected void addJobChangeListener(IJobChangeListener listener) {
+ listeners.add(listener);
+ }
+
+ /**
+ * Adds an entry at the end of the list of which this item is the head.
+ * @GuardedBy("manager.lock")
+ */
+ final void addLast(InternalJob entry) {
+ InternalJob last = this;
+ //find the end of the queue
+ while (last.previous != null)
+ last = last.previous;
+ //add the new entry to the end of the queue
+ last.previous = entry;
+ entry.next = last;
+ entry.previous = null;
+ }
+
+ protected boolean belongsTo(Object family) {
+ return false;
+ }
+
+ protected boolean cancel() {
+ return manager.cancel(this);
+ }
+
+ protected void canceling() {
+ //default implementation does nothing
+ }
+
+ @Override
+ public final int compareTo(Object otherJob) {
+ return ((InternalJob) otherJob).startTime >= startTime ? 1 : -1;
+ }
+
+ protected void done(IStatus endResult) {
+ manager.endJob(this, endResult, true);
+ }
+
+ /**
+ * Returns the job listeners that are only listening to this job. Never returns
+ * null.
+ */
+ final ListenerList getListeners() {
+ return listeners;
+ }
+
+ protected String getName() {
+ return name;
+ }
+
+ protected int getPriority() {
+ return priority;
+ }
+
+ /**
+ * Returns the job's progress monitor, or null if it is not running.
+ */
+ final IProgressMonitor getProgressMonitor() {
+ return monitor;
+ }
+
+ protected Object getProperty(QualifiedName key) {
+ // thread safety: (Concurrency001 - copy on write)
+ Map temp = properties;
+ if (temp == null)
+ return null;
+ return temp.get(key);
+ }
+
+ protected IStatus getResult() {
+ return result;
+ }
+
+ protected ISchedulingRule getRule() {
+ return schedulingRule;
+ }
+
+ /**
+ * Returns the time that this job should be started, awakened, or
+ * rescheduled, depending on the current state.
+ * @return time in milliseconds
+ */
+ final long getStartTime() {
+ return startTime;
+ }
+
+ protected int getState() {
+ int state = flags & M_STATE;
+ switch (state) {
+ //blocked and yielding state is equivalent to waiting state for clients
+ case YIELDING :
+ case BLOCKED :
+ return Job.WAITING;
+ case ABOUT_TO_RUN :
+ return Job.RUNNING;
+ case ABOUT_TO_SCHEDULE :
+ return Job.WAITING;
+ default :
+ return state;
+ }
+ }
+
+ protected Thread getThread() {
+ return thread;
+ }
+
+ protected JobGroup getJobGroup() {
+ return jobGroup;
+ }
+
+ /**
+ * Returns the raw job state, including internal states no exposed as API.
+ */
+ final int internalGetState() {
+ return flags & M_STATE;
+ }
+
+ /**
+ * Must be called from JobManager#setPriority
+ */
+ final void internalSetPriority(int newPriority) {
+ this.priority = newPriority;
+ }
+
+ /**
+ * Must be called from JobManager#setRule
+ */
+ final void internalSetRule(ISchedulingRule rule) {
+ this.schedulingRule = rule;
+ }
+
+ /**
+ * Must be called from JobManager#changeState
+ */
+ final void internalSetState(int i) {
+ flags = (flags & ~M_STATE) | i;
+ }
+
+ /**
+ * Returns whether this job was canceled when it was about to run
+ */
+ final boolean isAboutToRunCanceled() {
+ return (flags & M_ABOUT_TO_RUN_CANCELED) != 0;
+ }
+
+ /**
+ * Returns whether this job was canceled when it was running.
+ */
+ final boolean isRunCanceled() {
+ return (flags & M_RUN_CANCELED) != 0;
+ }
+
+ protected boolean isBlocking() {
+ return manager.isBlocking(this);
+ }
+
+ /**
+ * Returns true if this job conflicts with the given job, and false otherwise.
+ */
+ final boolean isConflicting(InternalJob otherJob) {
+ ISchedulingRule otherRule = otherJob.getRule();
+ if (schedulingRule == null || otherRule == null)
+ return false;
+ //if one of the rules is a compound rule, it must be asked the question.
+ if (schedulingRule.getClass() == MultiRule.class)
+ return schedulingRule.isConflicting(otherRule);
+ return otherRule.isConflicting(schedulingRule);
+ }
+
+ protected boolean isSystem() {
+ return (flags & M_SYSTEM) != 0;
+ }
+
+ protected boolean isUser() {
+ return (flags & M_USER) != 0;
+ }
+
+ protected void join() throws InterruptedException {
+ manager.join(this, 0, null);
+ }
+
+ protected boolean join(long timeout, IProgressMonitor joinMonitor) throws InterruptedException, OperationCanceledException {
+ return manager.join(this, timeout, joinMonitor);
+ }
+
+ /**
+ * Returns the next entry (ahead of this one) in the list, or null if there is no next entry
+ */
+ final InternalJob next() {
+ return next;
+ }
+
+ /**
+ * Returns the previous entry (behind this one) in the list, or null if there is no previous entry
+ */
+ final InternalJob previous() {
+ return previous;
+ }
+
+ /**
+ * Removes this entry from any list it belongs to. Returns the receiver.
+ */
+ final InternalJob remove() {
+ if (next != null)
+ next.setPrevious(previous);
+ if (previous != null)
+ previous.setNext(next);
+ next = previous = null;
+ return this;
+ }
+
+ protected void removeJobChangeListener(IJobChangeListener listener) {
+ listeners.remove(listener);
+ }
+
+ protected abstract IStatus run(IProgressMonitor progressMonitor);
+
+ protected void schedule(long delay) {
+ if (shouldSchedule())
+ manager.schedule(this, delay, false);
+ }
+
+ /**
+ * Sets whether this job was canceled when it was about to run
+ */
+ final void setAboutToRunCanceled(boolean value) {
+ flags = value ? flags | M_ABOUT_TO_RUN_CANCELED : flags & ~M_ABOUT_TO_RUN_CANCELED;
+
+ }
+
+ /**
+ * Sets whether this job was canceled when it was running
+ */
+ final void setRunCanceled(boolean value) {
+ flags = value ? flags | M_RUN_CANCELED : flags & ~M_RUN_CANCELED;
+ }
+
+ protected void setName(String name) {
+ Assert.isNotNull(name);
+ this.name = name;
+ }
+
+ /**
+ * Sets the next entry in this linked list of jobs.
+ * @param entry
+ */
+ final void setNext(InternalJob entry) {
+ this.next = entry;
+ }
+
+ /**
+ * Sets the previous entry in this linked list of jobs.
+ * @param entry
+ */
+ final void setPrevious(InternalJob entry) {
+ this.previous = entry;
+ }
+
+ protected void setPriority(int newPriority) {
+ switch (newPriority) {
+ case Job.INTERACTIVE :
+ case Job.SHORT :
+ case Job.LONG :
+ case Job.BUILD :
+ case Job.DECORATE :
+ manager.setPriority(this, newPriority);
+ break;
+ default :
+ throw new IllegalArgumentException(String.valueOf(newPriority));
+ }
+ }
+
+ protected void setProgressGroup(IProgressMonitor group, int ticks) {
+ Assert.isNotNull(group);
+ IProgressMonitor pm = manager.createMonitor(this, group, ticks);
+ if (pm != null)
+ setProgressMonitor(pm);
+ }
+
+ /**
+ * Sets the progress monitor to use for the next execution of this job,
+ * or for clearing the monitor when a job completes.
+ * @param monitor a progress monitor
+ */
+ final void setProgressMonitor(IProgressMonitor monitor) {
+ this.monitor = monitor;
+ }
+
+ protected void setProperty(QualifiedName key, Object value) {
+ // thread safety: (Concurrency001 - copy on write)
+ if (value == null) {
+ if (properties == null)
+ return;
+ ObjectMap temp = (ObjectMap) properties.clone();
+ temp.remove(key);
+ if (temp.isEmpty())
+ properties = null;
+ else
+ properties = temp;
+ } else {
+ ObjectMap temp = properties;
+ if (temp == null)
+ temp = new ObjectMap(5);
+ else
+ temp = (ObjectMap) properties.clone();
+ temp.put(key, value);
+ properties = temp;
+ }
+ }
+
+ /**
+ * Sets or clears the result of an execution of this job.
+ * @param result a result status, or null
+ * @GuardedBy("manager.lock")
+ */
+ final void setResult(IStatus result) {
+ this.result = result;
+ }
+
+ protected void setRule(ISchedulingRule rule) {
+ manager.setRule(this, rule);
+ }
+
+ /**
+ * Sets a time to start, wake up, or schedule this job,
+ * depending on the current state
+ * @param time a time in milliseconds
+ * @GuardedBy("manager.lock")
+ */
+ final void setStartTime(long time) {
+ startTime = time;
+ }
+
+ protected void setSystem(boolean value) {
+ if (getState() != Job.NONE)
+ throw new IllegalStateException();
+ flags = value ? flags | M_SYSTEM : flags & ~M_SYSTEM;
+ }
+
+ protected void setThread(Thread thread) {
+ this.thread = thread;
+ }
+
+ protected void setUser(boolean value) {
+ if (getState() != Job.NONE)
+ throw new IllegalStateException();
+ flags = value ? flags | M_USER : flags & ~M_USER;
+ }
+
+ protected void setJobGroup(JobGroup jobGroup) {
+ if (getState() != Job.NONE)
+ throw new IllegalStateException("Setting job group of an already scheduled job is not allowed"); //$NON-NLS-1$
+ this.jobGroup = jobGroup;
+ }
+
+ protected boolean shouldSchedule() {
+ return true;
+ }
+
+ protected boolean sleep() {
+ return manager.sleep(this);
+ }
+
+ protected Job yieldRule(IProgressMonitor progressMonitor) {
+ return manager.yieldRule(this, progressMonitor);
+ }
+
+ @Override
+ public String toString() {
+ return getName() + "(" + jobNumber + ")"; //$NON-NLS-1$//$NON-NLS-2$
+ }
+
+ protected void wakeUp(long delay) {
+ manager.wakeUp(this, delay);
+ }
+
+ /**
+ * @param waitQueueStamp The waitQueueStamp to set.
+ * @GuardedBy("manager.lock")
+ */
+ void setWaitQueueStamp(long waitQueueStamp) {
+ this.waitQueueStamp = waitQueueStamp;
+ }
+
+ /**
+ * @return Returns the waitQueueStamp.
+ * @GuardedBy("manager.lock")
+ */
+ long getWaitQueueStamp() {
+ return waitQueueStamp;
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/InternalJobGroup.java b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/InternalJobGroup.java
new file mode 100644
index 0000000000..f287685b8c
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/InternalJobGroup.java
@@ -0,0 +1,316 @@
+/*******************************************************************************
+ * Copyright (c) 2014 Google Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Thirumala Reddy Mutchukota - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.core.internal.jobs;
+
+import java.util.*;
+import org.eclipse.core.runtime.*;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.core.runtime.jobs.JobGroup;
+
+/**
+ * Internal implementation class for job groups.
+ *
+ * @noextend This class is not intended to be extended by clients. All job groups
+ * must be subclasses of the API org.eclipse.core.runtime.jobs.JobGroup class.
+ */
+public class InternalJobGroup {
+ /**
+ * The maximum amount of time to wait on {@link #jobGroupStateLock}.
+ * Determines how often the progress monitor is checked for cancellation.
+ */
+ private static final long MAX_WAIT_INTERVAL = 100;
+ /**
+ * This lock will be held while performing state changes on this job group. It is
+ * also used as a notifier to wake up the threads waiting for this job group to complete.
+ *
+ * External code is never called while holding this lock, thus removing the hold and wait
+ * condition necessary for deadlock.
+ *
+ * @GuardedBy("itself")
+ */
+ private final Object jobGroupStateLock = new Object();
+
+ private static final JobManager manager = JobManager.getInstance();
+
+ private final String name;
+ private final int maxThreads;
+
+ private volatile int state = JobGroup.NONE;
+ private volatile MultiStatus result;
+ private final Set runningJobs = new HashSet();
+ private final Set otherActiveJobs = new HashSet();
+ private final List results = new ArrayList();
+ private boolean cancelingDueToError;
+ private int failedJobsCount;
+ private int canceledJobsCount;
+ private int seedJobsCount;
+ private int seedJobsRemainingCount;
+
+ protected InternalJobGroup(String name, int maxThreads, int seedJobsCount) {
+ Assert.isNotNull(name);
+ Assert.isLegal(maxThreads >= 0);
+ Assert.isLegal(seedJobsCount > 0);
+ this.name = name;
+ this.maxThreads = maxThreads;
+ this.seedJobsCount = seedJobsCount;
+ this.seedJobsRemainingCount = seedJobsCount;
+ }
+
+ protected String getName() {
+ return name;
+ }
+
+ protected int getMaxThreads() {
+ return maxThreads;
+ }
+
+ protected MultiStatus getResult() {
+ return result;
+ }
+
+ protected int getState() {
+ return state;
+ }
+
+ protected List getActiveJobs() {
+ return manager.find(this);
+ }
+
+ protected void cancel() {
+ manager.cancel(this);
+ }
+
+ protected boolean join(long timeout, IProgressMonitor monitor) throws InterruptedException, OperationCanceledException {
+ return manager.join(this, timeout, monitor);
+ }
+
+ /**
+ * Called by the JobManager when the state of a job belonging to this group has changed.
+ * Must be called from JobManager#changeState
+ *
+ * @param job a job belonging to this group
+ * @param oldState the old state of the job
+ * @param newState the new state of the job
+ * @GuardedBy("JobManager.lock")
+ */
+ final void jobStateChanged(InternalJob job, int oldState, int newState) {
+ switch (oldState) {
+ case Job.NONE :
+ break;
+ case Job.SLEEPING :
+ case Job.WAITING :
+ otherActiveJobs.remove(job);
+ break;
+ case Job.RUNNING :
+ runningJobs.remove(job);
+ break;
+ default :
+ Assert.isLegal(false, "Invalid job state: " + job + ", state: " + oldState); //$NON-NLS-1$//$NON-NLS-2$
+ break;
+ }
+
+ switch (newState) {
+ case Job.NONE :
+ break;
+ case Job.SLEEPING :
+ case Job.WAITING :
+ otherActiveJobs.add(job);
+ break;
+ case Job.RUNNING :
+ runningJobs.add(job);
+ break;
+ default :
+ Assert.isLegal(false, "Invalid job state: " + job + ", state: " + newState); //$NON-NLS-1$//$NON-NLS-2$
+ break;
+ }
+
+ /*
+ * We can determine if the job that is being scheduled is one of its job group's "seed"
+ * jobs by retrieving the currently running job for this thread. If there is a running
+ * job on this thread or that job's job group is the same as this job's job group, then
+ * this job is being scheduled in response to discovering more work, so it is not one
+ * of its group's seed jobs, otherwise it is.
+ */
+ if (job.internalGetState() == InternalJob.ABOUT_TO_SCHEDULE && getGroupOfCurrentlyRunningJob() != job.getJobGroup()) {
+ seedJobsRemainingCount--;
+ }
+
+ if (oldState == Job.RUNNING && newState == Job.NONE) {
+ IStatus jobResult = job.getResult();
+ Assert.isLegal(jobResult != null);
+ if (cancelingDueToError && jobResult.getSeverity() == IStatus.CANCEL)
+ return;
+
+ results.add(jobResult);
+ int jobResultSeverity = jobResult.getSeverity();
+ if (jobResultSeverity == IStatus.ERROR) {
+ failedJobsCount++;
+ } else if (jobResultSeverity == IStatus.CANCEL) {
+ canceledJobsCount++;
+ }
+ }
+ //make sure this job group is running
+ if (getState() == JobGroup.NONE && getActiveJobsCount() > 0) {
+ synchronized (jobGroupStateLock) {
+ state = JobGroup.ACTIVE;
+ jobGroupStateLock.notifyAll();
+ }
+ }
+
+ }
+
+ /**
+ * Returns the job group of the job currently running in this thread. Will return null if
+ * either this thread is not running a job or the job is not part of a job group.
+ */
+ private JobGroup getGroupOfCurrentlyRunningJob() {
+ Job job = manager.currentJob();
+ return job == null ? null : job.getJobGroup();
+ }
+
+ /**
+ * Called by the JobManager to signify that the group canceling reason is changed.
+ * Must be called from JobManager#cancel(InternalJobGroup).
+ *
+ * @param cancelDueToError true if the group is getting canceled because
+ * the shouldCancel(IStatus, int, int) method returned true,
+ * false otherwise.
+ * @GuardedBy("JobManager.lock")
+ */
+ final void updateCancelingReason(boolean cancelDueToError) {
+ cancelingDueToError = cancelDueToError;
+ if (!cancelDueToError) {
+ // add a dummy cancel status to the results to make sure the combined status
+ // will be of severity CANCEL.
+ results.add(Status.CANCEL_STATUS);
+ }
+ }
+
+ /**
+ * Called by the JobManager to signify that the group is getting canceled.
+ * Must be called from JobManager#cancel(InternalJobGroup).
+ *
+ * @param cancelDueToError true if the group is getting canceled because
+ * the shouldCancel(IStatus, int, int) method returned true,
+ * false otherwise.
+ * @GuardedBy("JobManager.lock")
+ * @GuardedBy("jobGroupStateLock")
+ */
+ final void cancelAndNotify(boolean cancelDueToError) {
+ synchronized (jobGroupStateLock) {
+ state = JobGroup.CANCELING;
+ updateCancelingReason(cancelDueToError);
+ jobGroupStateLock.notifyAll();
+ }
+ for (Job job : internalGetActiveJobs())
+ job.cancel();
+
+ }
+
+ /**
+ * Called by the JobManager to notify the group when the last job belonging
+ * to the group has finished execution. Must be called from JobManager#updateJobGroup.
+ *
+ * @param groupResult the combined status of the job group
+ * @GuardedBy("JobManager.lock")
+ * @GuardedBy("jobGroupStateLock")
+ */
+ final void endJobGroup(MultiStatus groupResult) {
+ synchronized (jobGroupStateLock) {
+ if (seedJobsRemainingCount > 0 && !groupResult.matches(IStatus.CANCEL))
+ throw new IllegalStateException("Invalid initial jobs remaining count"); //$NON-NLS-1$
+ state = JobGroup.NONE;
+ result = groupResult;
+ results.clear();
+ cancelingDueToError = false;
+ failedJobsCount = 0;
+ canceledJobsCount = 0;
+ seedJobsRemainingCount = seedJobsCount;
+ jobGroupStateLock.notifyAll();
+ }
+ }
+
+ final List internalGetActiveJobs() {
+ List activeJobs = new ArrayList(runningJobs.size() + otherActiveJobs.size());
+ for (InternalJob job : runningJobs)
+ activeJobs.add((Job) job);
+ for (InternalJob job : otherActiveJobs)
+ activeJobs.add((Job) job);
+ return activeJobs;
+ }
+
+ /**
+ * Called by the JobManager when updating the job group status. Prevents the job group
+ * from entering the NONE state prematurely, which can happen if all scheduled jobs
+ * run to completion while the master thread still has more "seed" jobs to schedule.
+ *
+ * @return the count of initial jobs remaining to be scheduled
+ */
+ final int getseedJobsRemainingCount() {
+ return seedJobsRemainingCount;
+ }
+
+ final int getActiveJobsCount() {
+ return runningJobs.size() + otherActiveJobs.size();
+ }
+
+ final int getRunningJobsCount() {
+ return runningJobs.size();
+ }
+
+ final int getFailedJobsCount() {
+ return failedJobsCount;
+ }
+
+ final int getCanceledJobsCount() {
+ return canceledJobsCount;
+ }
+
+ final List getCompletedJobResults() {
+ return new ArrayList(results);
+ }
+
+ protected boolean shouldCancel(IStatus lastCompletedJobResult, int numberOfFailedJobs, int numberOfCanceledJobs) {
+ return numberOfFailedJobs > 0;
+ }
+
+ protected MultiStatus computeGroupResult(List jobResults) {
+ List importantResults = new ArrayList();
+ for (IStatus jobResult : jobResults) {
+ if (jobResult.getSeverity() != IStatus.OK)
+ importantResults.add(jobResult);
+ }
+ if (importantResults.isEmpty())
+ return new MultiStatus("org.eclipse.core.jobs", 0, name, null); //$NON-NLS-1$
+
+ String pluginId = importantResults.get(0).getPlugin();
+ IStatus[] groupResults = importantResults.toArray(new IStatus[importantResults.size()]);
+ return new MultiStatus(pluginId, 0, groupResults, name, null);
+ }
+
+ /**
+ * Implementation of joining a job group.
+ * @param remainingTime
+ * @return true if the join completed, and false otherwise (still waiting).
+ */
+ boolean doJoin(long remainingTime) throws InterruptedException {
+ synchronized (jobGroupStateLock) {
+ if (getState() == JobGroup.NONE)
+ return true;
+ // If remaining time is greater than MAX_WAIT_INTERVAL, sleep only for
+ // MAX_WAIT_INTERVAL instead to be more responsive to monitor cancellation.
+ long sleepTime = remainingTime != 0 && remainingTime <= MAX_WAIT_INTERVAL ? remainingTime : MAX_WAIT_INTERVAL;
+ jobGroupStateLock.wait(sleepTime);
+ return getState() == JobGroup.NONE;
+ }
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/InternalWorker.java b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/InternalWorker.java
new file mode 100644
index 0000000000..259c580ee6
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/InternalWorker.java
@@ -0,0 +1,76 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2012 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.jobs;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.jobs.Job;
+
+/**
+ * Used to perform internal JobManager tasks. Currently, this is limited to checking
+ * progress monitors while a thread is performing a blocking wait in ThreadJob.
+ */
+public class InternalWorker extends Thread {
+ private final JobManager manager;
+ /**
+ * @GuardedBy("manager.monitorStack")
+ */
+ private boolean canceled;
+
+ InternalWorker(JobManager manager) {
+ super("Worker-JM"); //$NON-NLS-1$
+ this.manager = manager;
+ }
+
+ /**
+ * Will loop until there are progress monitors to check. While there are monitors
+ * registered, it will check cancelation every 250ms, and if it is canceled it will
+ * interrupt the ThreadJob that is performing a blocking wait.
+ */
+ @Override
+ public void run() {
+ int timeout = 0;
+ synchronized (manager.monitorStack) {
+ while (!canceled) {
+ if (manager.monitorStack.isEmpty()) {
+ timeout = 0;
+ } else {
+ timeout = 250;
+ }
+ for (int i = 0; i < manager.monitorStack.size(); i++) {
+ Object[] o = manager.monitorStack.get(i);
+ IProgressMonitor monitor = (IProgressMonitor) o[1];
+ if (monitor.isCanceled()) {
+ Job job = (Job) o[0];
+ Thread t = job.getThread();
+ if (t != null) {
+ t.interrupt();
+ }
+ }
+ }
+ try {
+ manager.monitorStack.wait(timeout);
+ } catch (InterruptedException e) {
+ // loop
+ }
+ }
+ }
+ }
+
+ /**
+ * Terminate this thread. Once terminated, it cannot be restarted.
+ */
+ void cancel() {
+ synchronized (manager.monitorStack) {
+ canceled = true;
+ manager.monitorStack.notifyAll();
+ }
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobActivator.java b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobActivator.java
new file mode 100644
index 0000000000..2e48077447
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobActivator.java
@@ -0,0 +1,76 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2012 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.jobs;
+
+import java.util.Hashtable;
+import org.eclipse.core.runtime.jobs.IJobManager;
+import org.osgi.framework.*;
+
+/**
+ * The Jobs plugin class.
+ */
+public class JobActivator implements BundleActivator {
+
+ /**
+ * Eclipse property. Set to false to avoid registering JobManager
+ * as an OSGi service.
+ */
+ private static final String PROP_REGISTER_JOB_SERVICE = "eclipse.service.jobs"; //$NON-NLS-1$
+
+ /**
+ * The bundle associated this plug-in
+ */
+ private static BundleContext bundleContext;
+
+ /**
+ * This plugin provides a JobManager service.
+ */
+ private ServiceRegistration jobManagerService = null;
+
+ /**
+ * This method is called upon plug-in activation
+ */
+ @Override
+ public void start(BundleContext context) throws Exception {
+ bundleContext = context;
+ JobOSGiUtils.getDefault().openServices();
+
+ boolean shouldRegister = !"false".equalsIgnoreCase(context.getProperty(PROP_REGISTER_JOB_SERVICE)); //$NON-NLS-1$
+ if (shouldRegister)
+ registerServices();
+ }
+
+ /**
+ * This method is called when the plug-in is stopped
+ */
+ @Override
+ public void stop(BundleContext context) throws Exception {
+ unregisterServices();
+ JobManager.shutdown();
+ JobOSGiUtils.getDefault().closeServices();
+ bundleContext = null;
+ }
+
+ static BundleContext getContext() {
+ return bundleContext;
+ }
+
+ private void registerServices() {
+ jobManagerService = bundleContext.registerService(IJobManager.class.getName(), JobManager.getInstance(), new Hashtable());
+ }
+
+ private void unregisterServices() {
+ if (jobManagerService != null) {
+ jobManagerService.unregister();
+ jobManagerService = null;
+ }
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobChangeEvent.java b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobChangeEvent.java
new file mode 100644
index 0000000000..43d3dcf67b
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobChangeEvent.java
@@ -0,0 +1,63 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2015 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM - Initial API and implementation
+ * Terry Parker - Bug 457504, Publish a job group's final status to IJobChangeListeners
+ *******************************************************************************/
+package org.eclipse.core.internal.jobs;
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.jobs.IJobChangeEvent;
+import org.eclipse.core.runtime.jobs.Job;
+
+public class JobChangeEvent implements IJobChangeEvent {
+ /**
+ * The job on which this event occurred.
+ */
+ Job job = null;
+ /**
+ * The result returned by the job's run method, or null if
+ * not applicable.
+ */
+ IStatus result = null;
+ /**
+ * The result returned by the job's job group, if this event signals
+ * completion of the last job in a group, or null if not
+ * applicable.
+ */
+ IStatus jobGroupResult = null;
+ /**
+ * The amount of time to wait after scheduling the job before it should be run,
+ * or -1 if not applicable for this type of event.
+ */
+ long delay = -1;
+ /**
+ * Whether this job is being immediately rescheduled.
+ */
+ boolean reschedule = false;
+
+ @Override
+ public long getDelay() {
+ return delay;
+ }
+
+ @Override
+ public Job getJob() {
+ return job;
+ }
+
+ @Override
+ public IStatus getResult() {
+ return result;
+ }
+
+ @Override
+ public IStatus getJobGroupResult() {
+ return jobGroupResult;
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobListeners.java b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobListeners.java
new file mode 100644
index 0000000000..81a422b11e
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobListeners.java
@@ -0,0 +1,167 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2014 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM - Initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.jobs;
+
+import org.eclipse.core.internal.runtime.RuntimeLog;
+import org.eclipse.core.runtime.*;
+import org.eclipse.core.runtime.jobs.*;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * Responsible for notifying all job listeners about job lifecycle events. Uses a
+ * specialized iterator to ensure the complex iteration logic is contained in one place.
+ */
+class JobListeners {
+ interface IListenerDoit {
+ public void notify(IJobChangeListener listener, IJobChangeEvent event);
+ }
+
+ private final IListenerDoit aboutToRun = new IListenerDoit() {
+ @Override
+ public void notify(IJobChangeListener listener, IJobChangeEvent event) {
+ listener.aboutToRun(event);
+ }
+ };
+ private final IListenerDoit awake = new IListenerDoit() {
+ @Override
+ public void notify(IJobChangeListener listener, IJobChangeEvent event) {
+ listener.awake(event);
+ }
+ };
+ private final IListenerDoit done = new IListenerDoit() {
+ @Override
+ public void notify(IJobChangeListener listener, IJobChangeEvent event) {
+ listener.done(event);
+ }
+ };
+ private final IListenerDoit running = new IListenerDoit() {
+ @Override
+ public void notify(IJobChangeListener listener, IJobChangeEvent event) {
+ listener.running(event);
+ }
+ };
+ private final IListenerDoit scheduled = new IListenerDoit() {
+ @Override
+ public void notify(IJobChangeListener listener, IJobChangeEvent event) {
+ listener.scheduled(event);
+ }
+ };
+ private final IListenerDoit sleeping = new IListenerDoit() {
+ @Override
+ public void notify(IJobChangeListener listener, IJobChangeEvent event) {
+ listener.sleeping(event);
+ }
+ };
+ /**
+ * The global job listeners.
+ */
+ protected final ListenerList global = new ListenerList(ListenerList.IDENTITY);
+
+ /**
+ * TODO Could use an instance pool to re-use old event objects
+ */
+ static JobChangeEvent newEvent(Job job) {
+ JobChangeEvent instance = new JobChangeEvent();
+ instance.job = job;
+ return instance;
+ }
+
+ static JobChangeEvent newEvent(Job job, IStatus result) {
+ JobChangeEvent instance = new JobChangeEvent();
+ instance.job = job;
+ instance.result = result;
+ return instance;
+ }
+
+ static JobChangeEvent newEvent(Job job, long delay) {
+ JobChangeEvent instance = new JobChangeEvent();
+ instance.job = job;
+ instance.delay = delay;
+ return instance;
+ }
+
+ /**
+ * Process the given doit for all global listeners and all local listeners
+ * on the given job.
+ */
+ private void doNotify(final IListenerDoit doit, final IJobChangeEvent event) {
+ //notify all global listeners
+ Object[] listeners = global.getListeners();
+ int size = listeners.length;
+ for (int i = 0; i < size; i++) {
+ try {
+ if (listeners[i] != null)
+ doit.notify((IJobChangeListener) listeners[i], event);
+ } catch (Throwable e) {
+ handleException(listeners[i], e);
+ }
+ }
+ //notify all local listeners
+ listeners = ((InternalJob) event.getJob()).getListeners().getListeners();
+ size = listeners.length;
+ for (int i = 0; i < size; i++) {
+ try {
+ if (listeners[i] != null)
+ doit.notify((IJobChangeListener) listeners[i], event);
+ } catch (Throwable e) {
+ handleException(listeners[i], e);
+ }
+ }
+ }
+
+ private void handleException(Object listener, Throwable e) {
+ //this code is roughly copied from InternalPlatform.run(ISafeRunnable),
+ //but in-lined here for performance reasons
+ if (e instanceof OperationCanceledException)
+ return;
+ String pluginId = JobOSGiUtils.getDefault().getBundleId(listener);
+ if (pluginId == null)
+ pluginId = JobManager.PI_JOBS;
+ String message = NLS.bind(JobMessages.meta_pluginProblems, pluginId);
+ RuntimeLog.log(new Status(IStatus.ERROR, pluginId, JobManager.PLUGIN_ERROR, message, e));
+ }
+
+ public void add(IJobChangeListener listener) {
+ global.add(listener);
+ }
+
+ public void remove(IJobChangeListener listener) {
+ global.remove(listener);
+ }
+
+ public void aboutToRun(Job job) {
+ doNotify(aboutToRun, newEvent(job));
+ }
+
+ public void awake(Job job) {
+ doNotify(awake, newEvent(job));
+ }
+
+ public void done(Job job, IStatus result, boolean reschedule) {
+ JobChangeEvent event = newEvent(job, result);
+ event.reschedule = reschedule;
+ doNotify(done, event);
+ }
+
+ public void running(Job job) {
+ doNotify(running, newEvent(job));
+ }
+
+ public void scheduled(Job job, long delay, boolean reschedule) {
+ JobChangeEvent event = newEvent(job, delay);
+ event.reschedule = reschedule;
+ doNotify(scheduled, event);
+ }
+
+ public void sleeping(Job job) {
+ doNotify(sleeping, newEvent(job));
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobManager.java b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobManager.java
new file mode 100644
index 0000000000..25a13bf001
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobManager.java
@@ -0,0 +1,1805 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2015 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * Stephan Wahlbrink - Fix for bug 200997.
+ * Danail Nachev - Fix for bug 109898
+ * Mike Moreaty - Fix for bug 289790
+ * Oracle Corporation - Fix for bug 316839
+ * Thirumala Reddy Mutchukota (thirumala@google.com) -
+ * Bug 432049, JobGroup API and implementation
+ * Bug 105821, Support for Job#join with timeout and progress monitor
+ * Jan Koehnlein - Fix for bug 60964 (454698)
+ * Terry Parker - Bug 457504, Publish a job group's final status to IJobChangeListeners
+ *******************************************************************************/
+package org.eclipse.core.internal.jobs;
+
+//don't use ICU because this is used for debugging only (see bug 135785)
+import java.text.*;
+import java.util.*;
+import org.eclipse.core.internal.runtime.RuntimeLog;
+import org.eclipse.core.runtime.*;
+import org.eclipse.core.runtime.jobs.*;
+import org.eclipse.osgi.service.debug.DebugOptions;
+import org.eclipse.osgi.service.debug.DebugOptionsListener;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * Implementation of API type IJobManager
+ *
+ * Implementation note: all the data structures of this class are protected by a
+ * single lock object held as a private field in this class. The JobManager
+ * instance itself is not used because this class is publicly reachable, and
+ * third party clients may try to synchronize on it.
+ *
+ * There are various locks used and held throughout the JobManager
+ * implementation. When multiple locks interact, circular hold and waits must
+ * never happen, or a deadlock will occur. To prevent deadlocks, this is the
+ * order that locks must be acquired.
+ *
+ * WorkerPool -> JobManager.implicitJobs -> JobManager.lock ->
+ * InternalJob.jobStateLock or InternalJobGroup.jobGroupStateLock
+ *
+ * @ThreadSafe
+ */
+public class JobManager implements IJobManager, DebugOptionsListener {
+
+ /**
+ * The unique identifier constant of this plug-in.
+ */
+ public static final String PI_JOBS = "org.eclipse.core.jobs"; //$NON-NLS-1$
+
+ /**
+ * Status code constant indicating an error occurred while running a plug-in.
+ * For backward compatibility with Platform.PLUGIN_ERROR left at (value = 2).
+ */
+ public static final int PLUGIN_ERROR = 2;
+
+ /**
+ * Determines how often the progress monitor is checked for cancellation during the join call.
+ */
+ private static final long MAX_WAIT_INTERVAL = 100;
+
+ private static final String OPTION_DEADLOCK_ERROR = PI_JOBS + "/jobs/errorondeadlock"; //$NON-NLS-1$
+ private static final String OPTION_DEBUG_BEGIN_END = PI_JOBS + "/jobs/beginend"; //$NON-NLS-1$
+ private static final String OPTION_DEBUG_YIELDING = PI_JOBS + "/jobs/yielding"; //$NON-NLS-1$
+ private static final String OPTION_DEBUG_YIELDING_DETAILED = PI_JOBS + "/jobs/yielding/detailed"; //$NON-NLS-1$
+ private static final String OPTION_DEBUG_JOBS = PI_JOBS + "/jobs"; //$NON-NLS-1$
+ private static final String OPTION_DEBUG_JOBS_TIMING = PI_JOBS + "/jobs/timing"; //$NON-NLS-1$
+ private static final String OPTION_LOCKS = PI_JOBS + "/jobs/locks"; //$NON-NLS-1$
+ private static final String OPTION_SHUTDOWN = PI_JOBS + "/jobs/shutdown"; //$NON-NLS-1$
+
+ static boolean DEBUG = false;
+ static boolean DEBUG_BEGIN_END = false;
+ static boolean DEBUG_YIELDING = false;
+ static boolean DEBUG_YIELDING_DETAILED = false;
+ static boolean DEBUG_DEADLOCK = false;
+ static boolean DEBUG_LOCKS = false;
+ static boolean DEBUG_TIMING = false;
+ static boolean DEBUG_SHUTDOWN = false;
+ private static DateFormat DEBUG_FORMAT;
+
+ /**
+ * The singleton job manager instance. It must be a singleton because
+ * all job instances maintain a reference (as an optimization) and have no way
+ * of updating it.
+ */
+ private static JobManager instance;
+ /**
+ * Scheduling rule used for validation of client-defined rules.
+ */
+ private static final ISchedulingRule nullRule = new ISchedulingRule() {
+ @Override
+ public boolean contains(ISchedulingRule rule) {
+ return rule == this;
+ }
+
+ @Override
+ public boolean isConflicting(ISchedulingRule rule) {
+ return rule == this;
+ }
+ };
+
+ /**
+ * True if this manager is active, and false otherwise. A job manager
+ * starts out active, and becomes inactive if it has been shutdown.
+ */
+ private volatile boolean active = true;
+
+ final ImplicitJobs implicitJobs = new ImplicitJobs(this);
+
+ /**
+ * Listeners for the job lifecycle. It is important that the
+ * JobManager#JobGroupUpdater is the first one that is dispatched to, since
+ * it updates the JobChangeEvent#jobGroupStatus field, which other listeners
+ * may use.
+ */
+ private final JobListeners jobListeners = new JobListeners();
+
+ /**
+ * The lock for synchronizing all activity in the job manager. To avoid deadlock,
+ * this lock must never be held for extended periods, and must never be
+ * held while third party code is being called.
+ * @GuardedBy("itself")
+ */
+ private final Object lock = new Object();
+
+ /**
+ * A job listener to check for the cancellation and completion of the job groups.
+ */
+ private final IJobChangeListener jobGroupUpdater = new JobGroupUpdater(lock);
+
+ private final LockManager lockManager = new LockManager();
+
+ /**
+ * The pool of worker threads.
+ */
+ private WorkerPool pool;
+
+ /**
+ * @GuardedBy("lock")
+ */
+ private ProgressProvider progressProvider = null;
+ /**
+ * Jobs that are currently running. Should only be modified from changeState
+ * @GuardedBy("lock")
+ */
+ private final HashSet running;
+
+ /**
+ * Jobs that are currently yielding. Should only be modified from changeState
+ * @GuardedBy("lock")
+ */
+ private final HashSet yielding;
+
+ /**
+ * Jobs that are sleeping. Some sleeping jobs are scheduled to wake
+ * up at a given start time, while others will sleep indefinitely until woken.
+ * Should only be modified from changeState
+ * @GuardedBy("lock")
+ */
+ private final JobQueue sleeping;
+ /**
+ * True if this manager has been suspended, and false otherwise. A job manager
+ * starts out not suspended, and becomes suspended when suspend
+ * is invoked. Once suspended, no jobs will start running until resume
+ * is called.
+ * @GuardedBy("lock")
+ */
+ private boolean suspended = false;
+
+ /**
+ * jobs that are waiting to be run. Should only be modified from changeState
+ * @GuardedBy("lock")
+ */
+ private final JobQueue waiting;
+
+ /**
+ * ThreadJobs that are waiting to be run. Should only be modified from changeState
+ * @GuardedBy("lock")
+ */
+ final JobQueue waitingThreadJobs;
+
+ /**
+ * Counter to record wait queue insertion order.
+ * @GuardedBy("lock")
+ */
+ Counter waitQueueCounter = new Counter();
+
+ /**
+ * A set of progress monitors we must track cancellation requests for.
+ * @GuardedBy("itself")
+ */
+ final List
+ *
+ * @see IJobManager#newLock()
+ * @since 3.0
+ * @noimplement This interface is not intended to be implemented by clients.
+ * @noextend This interface is not intended to be extended by clients.
+ */
+public interface ILock {
+ /**
+ * Attempts to acquire this lock. If the lock is in use and the specified delay is
+ * greater than zero, the calling thread will block until one of the following happens:
+ *
+ *
This lock is available
+ *
The thread is interrupted
+ *
The specified delay has elapsed
+ *
+ *
+ * While a thread is waiting, locks it already owns may be granted to other threads
+ * if necessary to break a deadlock. In this situation, the calling thread may be blocked
+ * for longer than the specified delay. On returning from this call, the calling thread
+ * will once again have exclusive access to any other locks it owned upon entering
+ * the acquire method.
+ *
+ * @param delay the number of milliseconds to delay
+ * @return true if the lock was successfully acquired, and
+ * false otherwise.
+ * @exception InterruptedException if the thread was interrupted
+ */
+ public boolean acquire(long delay) throws InterruptedException;
+
+ /**
+ * Acquires this lock. If the lock is in use, the calling thread will block until the lock
+ * becomes available. If the calling thread owns several locks, it will be blocked
+ * until all threads it requires become available, or until the thread is interrupted.
+ * While a thread is waiting, its locks may be granted to other threads if necessary
+ * to break a deadlock. On returning from this call, the calling thread will
+ * have exclusive access to this lock, and any other locks it owned upon
+ * entering the acquire method.
+ *
+ * This implementation ignores attempts to interrupt the thread. If response to
+ * interruption is needed, use the method acquire(long)
+ */
+ public void acquire();
+
+ /**
+ * Returns the number of nested acquires on this lock that have not been released.
+ * This is the number of times that release() must be called before the lock is
+ * freed.
+ *
+ * @return the number of nested acquires that have not been released
+ */
+ public int getDepth();
+
+ /**
+ * Releases this lock. Locks must only be released by the thread that currently
+ * owns the lock.
+ */
+ public void release();
+}
diff --git a/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/ISchedulingRule.java b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/ISchedulingRule.java
new file mode 100644
index 0000000000..8225c1a74c
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/ISchedulingRule.java
@@ -0,0 +1,69 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2012 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM - Initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.runtime.jobs;
+
+/**
+ * Scheduling rules are used by jobs to indicate when they need exclusive access
+ * to a resource. Scheduling rules can also be applied synchronously to a thread
+ * using IJobManager.beginRule(ISchedulingRule) and
+ * IJobManager.endRule(ISchedulingRule). The job manager guarantees that
+ * no two jobs with conflicting scheduling rules will run concurrently.
+ * Multiple rules can be applied to a given thread only if the outer rule explicitly
+ * allows the nesting as specified by the contains method.
+ *
+ * Clients may implement this interface.
+ *
+ * @see Job#getRule()
+ * @see Job#setRule(ISchedulingRule)
+ * @see Job#schedule(long)
+ * @see IJobManager#beginRule(ISchedulingRule, org.eclipse.core.runtime.IProgressMonitor)
+ * @see IJobManager#endRule(ISchedulingRule)
+ * @since 3.0
+ */
+public interface ISchedulingRule {
+ /**
+ * Returns whether this scheduling rule completely contains another scheduling
+ * rule. Rules can only be nested within a thread if the inner rule is completely
+ * contained within the outer rule.
+ *
+ * Implementations of this method must obey the rules of a partial order relation
+ * on the set of all scheduling rules. In particular, implementations must be reflexive
+ * (a.contains(a) is always true), antisymmetric (a.contains(b) and b.contains(a) iff a.equals(b),
+ * and transitive (if a.contains(b) and b.contains(c), then a.contains(c)). Implementations
+ * of this method must return false when compared to a rule they
+ * know nothing about.
+ *
+ * @param rule the rule to check for containment
+ * @return true if this rule contains the given rule, and
+ * false otherwise.
+ */
+ public boolean contains(ISchedulingRule rule);
+
+ /**
+ * Returns whether this scheduling rule is compatible with another scheduling rule.
+ * If true is returned, then no job with this rule will be run at the
+ * same time as a job with the conflicting rule. If false is returned,
+ * then the job manager is free to run jobs with these rules at the same time.
+ *
+ * Implementations of this method must be reflexive, symmetric, and consistent,
+ * and must return false when compared to a rule they know
+ * nothing about.
+ *
+ * This method must return true if calling {@link #contains(ISchedulingRule)} on
+ * the same rule also returns true. This is required because it would otherwise
+ * allow two threads to be running concurrently with the same rule.
+ *
+ * @param rule the rule to check for conflicts
+ * @return true if the rule is conflicting, and false
+ * otherwise.
+ */
+ public boolean isConflicting(ISchedulingRule rule);
+}
diff --git a/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/Job.java b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/Job.java
new file mode 100644
index 0000000000..cbc34dee43
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/Job.java
@@ -0,0 +1,878 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2014 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * Thirumala Reddy Mutchukota (thirumala@google.com) -
+ * Bug 432049, JobGroup API and implementation
+ * Bug 105821, Support for Job#join with timeout and progress monitor
+ *******************************************************************************/
+package org.eclipse.core.runtime.jobs;
+
+import org.eclipse.core.internal.jobs.InternalJob;
+import org.eclipse.core.internal.jobs.JobManager;
+import org.eclipse.core.runtime.*;
+
+/**
+ * Jobs are units of runnable work that can be scheduled to be run with the job
+ * manager. Once a job has completed, it can be scheduled to run again (jobs are
+ * reusable).
+ *
+ * Jobs have a state that indicates what they are currently doing. When constructed,
+ * jobs start with a state value of NONE. When a job is scheduled
+ * to be run, it moves into the WAITING state. When a job starts
+ * running, it moves into the RUNNING state. When execution finishes
+ * (either normally or through cancelation), the state changes back to
+ * NONE.
+ *
+ * A job can also be in the SLEEPING state. This happens if a user
+ * calls Job.sleep() on a waiting job, or if a job is scheduled to run after a specified
+ * delay. Only jobs in the WAITING state can be put to sleep.
+ * Sleeping jobs can be woken at any time using Job.wakeUp(), which will put the
+ * job back into the WAITING state.
+ *
+ * Jobs can be assigned a priority that is used as a hint about how the job should
+ * be scheduled. There is no guarantee that jobs of one priority will be run before
+ * all jobs of lower priority. The javadoc for the various priority constants provide
+ * more detail about what each priority means. By default, jobs start in the
+ * LONG priority class.
+ *
+ * @see IJobManager
+ * @since 3.0
+ */
+public abstract class Job extends InternalJob implements IAdaptable {
+
+ /**
+ * Job status return value that is used to indicate asynchronous job completion.
+ * @see Job#run(IProgressMonitor)
+ * @see Job#done(IStatus)
+ */
+ public static final IStatus ASYNC_FINISH = new Status(IStatus.OK, JobManager.PI_JOBS, 1, "", null);//$NON-NLS-1$
+
+ /* Job priorities */
+ /**
+ * Job priority constant (value 10) for interactive jobs.
+ * Interactive jobs generally have priority over all other jobs.
+ * Interactive jobs should be either fast running or very low on CPU
+ * usage to avoid blocking other interactive jobs from running.
+ *
+ * @see #getPriority()
+ * @see #setPriority(int)
+ * @see #run(IProgressMonitor)
+ */
+ public static final int INTERACTIVE = 10;
+ /**
+ * Job priority constant (value 20) for short background jobs.
+ * Short background jobs are jobs that typically complete within a second,
+ * but may take longer in some cases. Short jobs are given priority
+ * over all other jobs except interactive jobs.
+ *
+ * @see #getPriority()
+ * @see #setPriority(int)
+ * @see #run(IProgressMonitor)
+ */
+ public static final int SHORT = 20;
+ /**
+ * Job priority constant (value 30) for long-running background jobs.
+ *
+ * @see #getPriority()
+ * @see #setPriority(int)
+ * @see #run(IProgressMonitor)
+ */
+ public static final int LONG = 30;
+ /**
+ * Job priority constant (value 40) for build jobs. Build jobs are
+ * generally run after all other background jobs complete.
+ *
+ * @see #getPriority()
+ * @see #setPriority(int)
+ * @see #run(IProgressMonitor)
+ */
+ public static final int BUILD = 40;
+
+ /**
+ * Job priority constant (value 50) for decoration jobs.
+ * Decoration jobs have lowest priority. Decoration jobs generally
+ * compute extra information that the user may be interested in seeing
+ * but is generally not waiting for.
+ *
+ * @see #getPriority()
+ * @see #setPriority(int)
+ * @see #run(IProgressMonitor)
+ */
+ public static final int DECORATE = 50;
+ /**
+ * Job state code (value 0) indicating that a job is not
+ * currently sleeping, waiting, or running (i.e., the job manager doesn't know
+ * anything about the job).
+ *
+ * @see #getState()
+ */
+ public static final int NONE = 0;
+ /**
+ * Job state code (value 1) indicating that a job is sleeping.
+ *
+ * @see #run(IProgressMonitor)
+ * @see #getState()
+ */
+ public static final int SLEEPING = 0x01;
+ /**
+ * Job state code (value 2) indicating that a job is waiting to run.
+ *
+ * @see #getState()
+ * @see #yieldRule(IProgressMonitor)
+ */
+ public static final int WAITING = 0x02;
+ /**
+ * Job state code (value 4) indicating that a job is currently running
+ *
+ * @see #getState()
+ */
+ public static final int RUNNING = 0x04;
+
+ /**
+ * Returns the job manager.
+ *
+ * @return the job manager
+ * @since org.eclipse.core.jobs 3.2
+ */
+ public static final IJobManager getJobManager() {
+ return manager;
+ }
+
+ /**
+ * Creates a new Job that will execute the provided function
+ * when it runs.
+ *
+ * @param name The name of the job
+ * @param function The function to execute
+ * @return A job that encapsulates the provided function
+ * @see IJobFunction
+ * @since 3.6
+ */
+ public static Job create(String name, final IJobFunction function) {
+ return new Job(name) {
+ @Override
+ protected IStatus run(IProgressMonitor monitor) {
+ return function.run(monitor);
+ }
+ };
+ }
+
+ /**
+ * Creates a new job with the specified name. The job name is a human-readable
+ * value that is displayed to users. The name does not need to be unique, but it
+ * must not be null.
+ *
+ * @param name the name of the job.
+ */
+ public Job(String name) {
+ super(name);
+ }
+
+ /**
+ * Registers a job listener with this job
+ * Has no effect if an identical listener is already registered.
+ *
+ * @param listener the listener to be added.
+ */
+ @Override
+ public final void addJobChangeListener(IJobChangeListener listener) {
+ super.addJobChangeListener(listener);
+ }
+
+ /**
+ * Returns whether this job belongs to the given family. Job families are
+ * represented as objects that are not interpreted or specified in any way
+ * by the job manager. Thus, a job can choose to belong to any number of
+ * families.
+ *
+ * Clients may override this method. This default implementation always returns
+ * false. Overriding implementations must return false
+ * for families they do not recognize.
+ *
+ *
+ * @param family the job family identifier
+ * @return true if this job belongs to the given family, and
+ * false otherwise.
+ */
+ @Override
+ public boolean belongsTo(Object family) {
+ return false;
+ }
+
+ /**
+ * Stops the job. If the job is currently waiting,
+ * it will be removed from the queue. If the job is sleeping,
+ * it will be discarded without having a chance to resume and its sleeping state
+ * will be cleared. If the job is currently executing, it will be asked to
+ * stop but there is no guarantee that it will do so.
+ *
+ * @return false if the job is currently running (and thus may not
+ * respond to cancelation), and true in all other cases.
+ */
+ @Override
+ public final boolean cancel() {
+ return super.cancel();
+ }
+
+ /**
+ * A hook method indicating that this job is running and {@link #cancel()}
+ * is being called for the first time.
+ *
+ * Subclasses may override this method to perform additional work when
+ * a cancelation request is made. This default implementation does nothing.
+ * @since 3.3
+ */
+ @Override
+ protected void canceling() {
+ //default implementation does nothing
+ }
+
+ /**
+ * Jobs that complete their execution asynchronously must indicate when they
+ * are finished by calling this method. This method must not be called by
+ * a job that has not indicated that it is executing asynchronously.
+ *
+ * This method must not be called from within the scope of a job's run
+ * method. Jobs should normally indicate completion by returning an appropriate
+ * status from the run method. Jobs that return a status of
+ * ASYNC_FINISH from their run method must later call
+ * done to indicate completion.
+ *
+ * @param result a status object indicating the result of the job's execution.
+ * @see #ASYNC_FINISH
+ * @see #run(IProgressMonitor)
+ */
+ @Override
+ public final void done(IStatus result) {
+ super.done(result);
+ }
+
+ /**
+ * Returns the human readable name of this job. The name is never
+ * null.
+ *
+ * @return the name of this job
+ */
+ @Override
+ public final String getName() {
+ return super.getName();
+ }
+
+ /**
+ * Returns the priority of this job. The priority is used as a hint when the job
+ * is scheduled to be run.
+ *
+ * @return the priority of the job. One of INTERACTIVE, SHORT, LONG, BUILD,
+ * or DECORATE.
+ */
+ @Override
+ public final int getPriority() {
+ return super.getPriority();
+ }
+
+ /**
+ * Returns the value of the property of this job identified by the given key,
+ * or null if this job has no such property.
+ *
+ * @param key the name of the property
+ * @return the value of the property,
+ * or null if this job has no such property
+ * @see #setProperty(QualifiedName, Object)
+ */
+ @Override
+ public final Object getProperty(QualifiedName key) {
+ return super.getProperty(key);
+ }
+
+ /**
+ * Returns the result of this job's last run.
+ *
+ * @return the result of this job's last run, or null if this
+ * job has never finished running.
+ */
+ @Override
+ public final IStatus getResult() {
+ return super.getResult();
+ }
+
+ /**
+ * Returns the scheduling rule for this job. Returns null if this job has no
+ * scheduling rule.
+ *
+ * @return the scheduling rule for this job, or null.
+ * @see ISchedulingRule
+ * @see #setRule(ISchedulingRule)
+ */
+ @Override
+ public final ISchedulingRule getRule() {
+ return super.getRule();
+ }
+
+ /**
+ * Returns the state of the job. Result will be one of:
+ *
+ *
Job.RUNNING - if the job is currently running.
+ *
Job.WAITING - if the job is waiting to be run.
+ *
Job.SLEEPING - if the job is sleeping.
+ *
Job.NONE - in all other cases.
+ *
+ *
+ * Note that job state is inherently volatile, and in most cases clients
+ * cannot rely on the result of this method being valid by the time the
+ * result is obtained. For example, if getState returns
+ * RUNNING, the job may have actually completed by the
+ * time the getState method returns. All clients can infer from
+ * invoking this method is that the job was recently in the returned state.
+ *
+ * @return the job state
+ */
+ @Override
+ public final int getState() {
+ return super.getState();
+ }
+
+ /**
+ * Returns the thread that this job is currently running in.
+ *
+ * @return the thread this job is running in, or null
+ * if this job is not running or the thread is unknown.
+ */
+ @Override
+ public final Thread getThread() {
+ return super.getThread();
+ }
+
+ /**
+ * Returns the job group this job belongs to, or null if this
+ * job does not belongs to any group.
+ *
+ * @return the job group this job belongs to, or null.
+ * @see JobGroup
+ * @see #setJobGroup(JobGroup)
+ * @since 3.7
+ */
+ @Override
+ public final JobGroup getJobGroup() {
+ return super.getJobGroup();
+ }
+
+ /**
+ * Returns whether this job is blocking a higher priority non-system job from
+ * starting due to a conflicting scheduling rule. Returns false
+ * if this job is not running, or is not blocking a higher priority non-system job.
+ *
+ * @return true if this job is blocking a higher priority non-system
+ * job, and false otherwise.
+ * @see #getRule()
+ * @see #isSystem()
+ */
+ @Override
+ public final boolean isBlocking() {
+ return super.isBlocking();
+ }
+
+ /**
+ * Returns whether this job is a system job. System jobs are typically not
+ * revealed to users in any UI presentation of jobs. Other than their UI presentation,
+ * system jobs act exactly like other jobs. If this value is not explicitly set, jobs
+ * are treated as non-system jobs. The default value is false.
+ *
+ * @return true if this job is a system job, and
+ * false otherwise.
+ * @see #setSystem(boolean)
+ */
+ @Override
+ public final boolean isSystem() {
+ return super.isSystem();
+ }
+
+ /**
+ * Returns whether this job has been directly initiated by a UI end user.
+ * These jobs may be presented differently in the UI. The default value
+ * is false.
+ *
+ * @return true if this job is a user-initiated job, and
+ * false otherwise.
+ * @see #setUser(boolean)
+ */
+ @Override
+ public final boolean isUser() {
+ return super.isUser();
+ }
+
+ /**
+ * Waits until this job is finished. This method will block the calling thread until the
+ * job has finished executing, or until this thread has been interrupted. If the job
+ * has not been scheduled, this method returns immediately. A job must not
+ * be joined from within the scope of its run method.
+ *
+ * If this method is called on a job that reschedules itself from within the
+ * run method, the join will return at the end of the first execution.
+ * In other words, join will return the first time this job exits the
+ * {@link #RUNNING} state, or as soon as this job enters the {@link #NONE} state.
+ *
+ *
+ * If this method is called while the job manager is suspended, this job
+ * will only be joined if it is already running; if this job is waiting or sleeping,
+ * this method returns immediately.
+ *
+ *
+ * Note that there is a deadlock risk when using join. If the calling thread owns
+ * a lock or object monitor that the joined thread is waiting for, deadlock
+ * will occur.
+ *
+ *
+ * Joining on another job belonging to the same group is not allowed if the
+ * group enforces throttling due to the potential for deadlock. For example,
+ * when the maximum threads allowed is set to 1 and a currently running Job A
+ * issues a join on another Job B belonging to its own job group, A waits
+ * indefinitely for its join to finish, but B never gets to run. To avoid that
+ * an IllegalStateException is thrown when a job tries to join another job
+ * belonging to the same job group. Joining another job belonging to the
+ * same group is allowed when the job group does not enforce throttling
+ * (JobGroup#getMaxThreads is zero).
+ *
+ *
+ * Calling this method is equivalent to calling join(0, null) and
+ * it is recommended to use the other join method with timeout and progress monitor
+ * as that will provide more control over the join operation.
+ *
+ *
+ * @exception InterruptedException if this thread is interrupted while waiting
+ * @exception IllegalStateException when a job tries to join on itself or join on
+ * another job belonging to the same job group and the group is configured with
+ * non zero maximum threads allowed.
+ * @see #setJobGroup(JobGroup)
+ * @see #join(long, IProgressMonitor)
+ * @see ILock
+ * @see IJobManager#suspend()
+ */
+ @Override
+ public final void join() throws InterruptedException {
+ super.join();
+ }
+
+ /**
+ * Waits until either the job is finished or the given timeout has expired.
+ * This method will block the calling thread until the job has finished executing,
+ * or the given timeout is expired, or the given progress monitor is canceled by the user
+ * or the calling thread is interrupted. If the job has not been scheduled,
+ * this method returns immediately. A job must not be joined from within the scope of
+ * its run method.
+ *
+ * If this method is called on a job that reschedules itself from within the
+ * run method, the join will return at the end of the first execution.
+ * In other words, join will return the first time this job exits the
+ * {@link #RUNNING} state, or as soon as this job enters the {@link #NONE} state.
+ *
+ *
+ * If this method is called while the job manager is suspended, this job
+ * will only be joined if it is already running; if this job is waiting or sleeping,
+ * this method returns immediately.
+ *
+ *
+ * Note that there is a deadlock risk when using join. If the calling thread owns
+ * a lock or object monitor that the joined thread is waiting for and the timeout
+ * is set zero (i.e no timeout), deadlock will occur.
+ *
+ *
+ * Joining on another job belonging to the same group is not allowed if the
+ * timeout is set to zero and the group enforces throttling due to the potential
+ * for deadlock. For example, when the maximum threads allowed is set to 1 and
+ * a currently running Job A issues a join with no timeout on another Job B
+ * belonging to its own job group, A waits indefinitely for its join to finish,
+ * but B never gets to run. To avoid that an IllegalStateException is thrown when
+ * a job tries to join (with no timeout) another job belonging to the same job group.
+ * Joining another job belonging to the same group is allowed when either the job group
+ * does not enforce throttling (JobGroup#getMaxThreads is zero) or a non zero timeout
+ * value is provided.
+ *
+ *
+ * Throws an OperationCanceledException when the given progress monitor
+ * is canceled. Canceling the monitor does not cancel the job and, if required,
+ * the job may be canceled explicitly using the {@link #cancel()} method.
+ *
+ *
+ * @param timeoutMillis the maximum amount of time to wait for the join to complete,
+ * or zero for no timeout.
+ * @param monitor the progress monitor that can be used to cancel the join operation,
+ * or null if cancellation is not required. No progress is reported
+ * on this monitor.
+ * @return true when the job completes, or false when
+ * the operation is not completed within the given time.
+ * @exception InterruptedException if this thread is interrupted while waiting
+ * @exception IllegalStateException when a job tries to join on itself or join with
+ * no timeout on another job belonging to the same job group and the group is configured
+ * with non-zero maximum threads allowed.
+ * @exception OperationCanceledException if the progress monitor is canceled while waiting
+ * @see #setJobGroup(JobGroup)
+ * @see #cancel()
+ * @see ILock
+ * @see IJobManager#suspend()
+ * @since 3.7
+ */
+ @Override
+ public final boolean join(long timeoutMillis, IProgressMonitor monitor) throws InterruptedException, OperationCanceledException {
+ return super.join(timeoutMillis, monitor);
+ }
+
+ /**
+ * Removes a job listener from this job.
+ * Has no effect if an identical listener is not already registered.
+ *
+ * @param listener the listener to be removed
+ */
+ @Override
+ public final void removeJobChangeListener(IJobChangeListener listener) {
+ super.removeJobChangeListener(listener);
+ }
+
+ /**
+ * Executes this job. Returns the result of the execution.
+ *
+ * The provided monitor can be used to report progress and respond to
+ * cancellation. If the progress monitor has been canceled, the job
+ * should finish its execution at the earliest convenience and return a result
+ * status of severity {@link IStatus#CANCEL}. The singleton
+ * cancel status {@link Status#CANCEL_STATUS} can be used for
+ * this purpose. The monitor is only valid for the duration of the invocation
+ * of this method.
+ *
+ * This method must not be called directly by clients. Clients should call
+ * schedule, which will in turn cause this method to be called.
+ *
+ * Jobs can optionally finish their execution asynchronously (in another thread) by
+ * returning a result status of {@link #ASYNC_FINISH}. Jobs that finish
+ * asynchronously must specify the execution thread by calling
+ * setThread, and must indicate when they are finished by calling
+ * the method done.
+ *
+ * @param monitor the monitor to be used for reporting progress and
+ * responding to cancelation. The monitor is never null
+ * @return resulting status of the run. The result must not be null
+ * @see #ASYNC_FINISH
+ * @see #done(IStatus)
+ */
+ @Override
+ protected abstract IStatus run(IProgressMonitor monitor);
+
+ /**
+ * Schedules this job to be run. The job is added to a queue of waiting
+ * jobs, and will be run when it arrives at the beginning of the queue.
+ *
+ * This is a convenience method, fully equivalent to
+ * schedule(0L).
+ *
+ * @see #schedule(long)
+ */
+ public final void schedule() {
+ super.schedule(0L);
+ }
+
+ /**
+ * Schedules this job to be run after a specified delay. The job is put in the
+ * {@link #SLEEPING} state until the specified delay has elapsed, after which
+ * the job is added to a queue of {@link #WAITING} jobs. Once the job arrives
+ * at the beginning of the queue, it will be run at the first available opportunity.
+ *
+ * Jobs of equal priority and delay with conflicting scheduling
+ * rules are guaranteed to run in the order they are scheduled. No guarantees
+ * are made about the relative execution order of jobs with unrelated or
+ * null scheduling rules, or different priorities.
+ *
+ * If this job is currently running, it will be rescheduled with the specified
+ * delay as soon as it finishes. If this method is called multiple times
+ * while the job is running, the job will still only be rescheduled once,
+ * with the most recent delay value that was provided.
+ *
+ * Scheduling a job that is waiting or sleeping has no effect.
+ *
+ *
+ * @param delay a time delay in milliseconds before the job should run
+ * @see ISchedulingRule
+ */
+ @Override
+ public final void schedule(long delay) {
+ super.schedule(delay);
+ }
+
+ /**
+ * Changes the name of this job. If the job is currently running, waiting,
+ * or sleeping, the new job name may not take effect until the next time the
+ * job is scheduled.
+ *
+ * The job name is a human-readable value that is displayed to users. The name
+ * does not need to be unique, but it must not be null.
+ *
+ * @param name the name of the job.
+ */
+ @Override
+ public final void setName(String name) {
+ super.setName(name);
+ }
+
+ /**
+ * Sets the priority of the job. This will not affect the execution of
+ * a running job, but it will affect how the job is scheduled while
+ * it is waiting to be run.
+ *
+ * @param priority the new job priority. One of
+ * INTERACTIVE, SHORT, LONG, BUILD, or DECORATE.
+ */
+ @Override
+ public final void setPriority(int priority) {
+ super.setPriority(priority);
+ }
+
+ /**
+ * Associates this job with a progress group. Progress feedback
+ * on this job's next execution will be displayed together with other
+ * jobs in that group. The provided monitor must be a monitor
+ * created by the method IJobManager.createProgressGroup
+ * and must have at least ticks units of available work.
+ *
+ * The progress group must be set before the job is scheduled.
+ * The group will be used only for a single invocation of the job's
+ * run method, after which any association of this job to the
+ * group will be lost.
+ *
+ * @see IJobManager#createProgressGroup()
+ * @param group The progress group to use for this job
+ * @param ticks the number of work ticks allocated from the
+ * parent monitor, or {@link IProgressMonitor#UNKNOWN}
+ */
+ @Override
+ public final void setProgressGroup(IProgressMonitor group, int ticks) {
+ super.setProgressGroup(group, ticks);
+ }
+
+ /**
+ * Sets the value of the property of this job identified
+ * by the given key. If the supplied value is null,
+ * the property is removed from this resource.
+ *
+ * Properties are intended to be used as a caching mechanism
+ * by ISV plug-ins. They allow key-object associations to be stored with
+ * a job instance. These key-value associations are maintained in
+ * memory (at all times), and the information is never discarded automatically.
+ *
+ * The qualifier part of the property name must be the unique identifier
+ * of the declaring plug-in (e.g. "com.example.plugin").
+ *
+ *
+ * @param key the qualified name of the property
+ * @param value the value of the property,
+ * or null if the property is to be removed
+ * @see #getProperty(QualifiedName)
+ */
+ @Override
+ public void setProperty(QualifiedName key, Object value) {
+ super.setProperty(key, value);
+ }
+
+ /**
+ * Sets the scheduling rule to be used when scheduling this job. This method
+ * must be called before the job is scheduled.
+ *
+ * @param rule the new scheduling rule, or null if the job
+ * should have no scheduling rule
+ * @see #getRule()
+ */
+ @Override
+ public final void setRule(ISchedulingRule rule) {
+ super.setRule(rule);
+ }
+
+ /**
+ * Sets whether or not this job is a system job. System jobs are typically not
+ * revealed to users in any UI presentation of jobs. Other than their UI presentation,
+ * system jobs act exactly like other jobs. If this value is not explicitly set, jobs
+ * are treated as non-system jobs. This method must be called before the job
+ * is scheduled.
+ *
+ * @param value true if this job should be a system job, and
+ * false otherwise.
+ * @see #isSystem()
+ */
+ @Override
+ public final void setSystem(boolean value) {
+ super.setSystem(value);
+ }
+
+ /**
+ * Sets whether or not this job has been directly initiated by a UI end user.
+ * These jobs may be presented differently in the UI. This method must be
+ * called before the job is scheduled.
+ *
+ * @param value true if this job is a user-initiated job, and
+ * false otherwise.
+ * @see #isUser()
+ */
+ @Override
+ public final void setUser(boolean value) {
+ super.setUser(value);
+ }
+
+ /**
+ * Sets the thread that this job is currently running in, or null
+ * if this job is not running or the thread is unknown.
+ *
+ * Jobs that use the {@link #ASYNC_FINISH} return code should tell
+ * the job what thread it is running in. This is used to prevent deadlocks.
+ *
+ * @param thread the thread that this job is running in.
+ *
+ * @see #ASYNC_FINISH
+ * @see #run(IProgressMonitor)
+ */
+ @Override
+ public final void setThread(Thread thread) {
+ super.setThread(thread);
+ }
+
+ /**
+ * Sets the job group to which this job belongs. This method must be called before
+ * the job is scheduled, otherwise an IllegalStateException is thrown.
+ *
+ * @param jobGroup the group to which this job belongs to, or null if
+ * this job does not belongs to any group.
+ * @see JobGroup
+ * @since 3.7
+ */
+ @Override
+ public final void setJobGroup(JobGroup jobGroup) {
+ super.setJobGroup(jobGroup);
+ }
+
+ /**
+ * Returns whether this job should be run.
+ * If false is returned, this job will be discarded by the job manager
+ * without running.
+ *
+ * This method is called immediately prior to calling the job's
+ * run method, so it can be used for last minute precondition checking before
+ * a job is run. This method must not attempt to schedule or change the
+ * state of any other job.
+ *
+ * Clients may override this method. This default implementation always returns
+ * true.
+ *
+ *
+ * @return true if this job should be run
+ * and false otherwise
+ */
+ public boolean shouldRun() {
+ return true;
+ }
+
+ /**
+ * Returns whether this job should be scheduled.
+ * If false is returned, this job will be discarded by the job manager
+ * without being added to the queue.
+ *
+ * This method is called immediately prior to adding the job to the waiting job
+ * queue.,so it can be used for last minute precondition checking before
+ * a job is scheduled.
+ *
+ * Clients may override this method. This default implementation always returns
+ * true.
+ *
+ *
+ * @return true if the job manager should schedule this job
+ * and false otherwise
+ */
+ @Override
+ public boolean shouldSchedule() {
+ return true;
+ }
+
+ /**
+ * Requests that this job be suspended. If the job is currently waiting to be run, it
+ * will be removed from the queue move into the {@link #SLEEPING} state.
+ * The job will remain asleep until either resumed or canceled. If this job is not
+ * currently waiting to be run, this method has no effect.
+ *
+ * Sleeping jobs can be resumed using wakeUp.
+ *
+ * @return false if the job is currently running (and thus cannot
+ * be put to sleep), and true in all other cases
+ * @see #wakeUp()
+ */
+ @Override
+ public final boolean sleep() {
+ return super.sleep();
+ }
+
+ /**
+ * Returns a string representation of this job to be used for debugging purposes only.
+ * @since org.eclipse.core.jobs 3.5
+ */
+ @Override
+ public String toString() {
+ return super.toString();
+ }
+
+ /**
+ * Puts this job immediately into the {@link #WAITING} state so that it is
+ * eligible for immediate execution. If this job is not currently sleeping,
+ * the request is ignored.
+ *
+ * This is a convenience method, fully equivalent to
+ * wakeUp(0L).
+ *
+ * @see #sleep()
+ */
+ public final void wakeUp() {
+ super.wakeUp(0L);
+ }
+
+ /**
+ * Puts this job back into the {@link #WAITING} state after
+ * the specified delay. This is equivalent to canceling the sleeping job and
+ * rescheduling with the given delay. If this job is not currently sleeping,
+ * the request is ignored.
+ *
+ * @param delay the number of milliseconds to delay
+ * @see #sleep()
+ */
+ @Override
+ public final void wakeUp(long delay) {
+ super.wakeUp(delay);
+ }
+
+ /**
+ * Temporarily puts this Job back into {@link #WAITING} state and
+ * relinquishes the job's scheduling rule so that any {@link #WAITING} jobs that
+ * conflict with this job's scheduling rule have an opportunity to start. This
+ * method will wait until the rule this job held prior to invoking this method is
+ * re-acquired. This method has no effect and returns null if there are no
+ * {@link #WAITING} jobs that conflict with this job's scheduling rule.
Note:
+ * If this job has acquired any other locks, implicit or explicit, they will
+ * not be released. This may increase the risk of deadlock, so this method
+ * should only be used when it is known that the environment is safe.
This
+ * method must be invoked by this job's Thread, and only when it is
+ * {@link #RUNNING} state.
+ *
+ * @param monitor a progress monitor, or null if progress
+ * reporting is not desired. Cancellation attempts will be ignored.
+ * @return The Job that was {@link #WAITING}, and blocked by this Job
+ * (at the time this method was invoked) that was unblocked and allowed a chance
+ * to run, or null if no jobs were unblocked. Note: it is not guaranteed
+ * that this Job resume immediately if other conflicting jobs are
+ * also waiting after the unblocked job ends.
+ *
+ * @since org.eclipse.core.jobs 3.5
+ * @see Job#getRule()
+ * @see Job#isBlocking()
+ */
+ @Override
+ public Job yieldRule(IProgressMonitor monitor) {
+ return super.yieldRule(monitor);
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/JobChangeAdapter.java b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/JobChangeAdapter.java
new file mode 100644
index 0000000000..6c93aaa69d
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/JobChangeAdapter.java
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2012 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM - Initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.runtime.jobs;
+
+/**
+ * This adapter class provides default implementations for the
+ * methods described by the IJobChangeListener interface.
+ *
+ * Classes that wish to listen to the progress of scheduled jobs can
+ * extend this class and override only the methods which they are
+ * interested in.
+ *
+ *
+ * @see IJobChangeListener
+ * @since 3.0
+ */
+public class JobChangeAdapter implements IJobChangeListener {
+ @Override
+ public void aboutToRun(IJobChangeEvent event) {
+ // do nothing
+ }
+
+ @Override
+ public void awake(IJobChangeEvent event) {
+ // do nothing
+ }
+
+ @Override
+ public void done(IJobChangeEvent event) {
+ // do nothing
+ }
+
+ @Override
+ public void running(IJobChangeEvent event) {
+ // do nothing
+ }
+
+ @Override
+ public void scheduled(IJobChangeEvent event) {
+ // do nothing
+ }
+
+ @Override
+ public void sleeping(IJobChangeEvent event) {
+ // do nothing
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/JobGroup.java b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/JobGroup.java
new file mode 100644
index 0000000000..93814251b1
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/JobGroup.java
@@ -0,0 +1,265 @@
+/*******************************************************************************
+ * Copyright (c) 2014 Google Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Thirumala Reddy Mutchukota - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.core.runtime.jobs;
+
+import java.util.List;
+import org.eclipse.core.internal.jobs.InternalJobGroup;
+import org.eclipse.core.runtime.*;
+
+/**
+ * JobGroups support throttling, join, cancel, combined progress and error reporting
+ * on a group of jobs.
+ *
+ *
A JobGroup object represents a group of Jobs. Any number of jobs
+ * can be added to a group, but a Job can be part of only one group at a time.
+ *
A JobGroup can be configured with a throttling number, so that only that many
+ * jobs from the group are allowed to run in parallel.
+ *
One can join on all of the jobs in the group and observe the completion progress
+ * of those jobs.
+ *
One can cancel all the jobs in the group.
+ *
A JobGroup consolidates the return statuses of all the jobs in the group
+ * and a single MultiStatus message is available after all the jobs in the group
+ * are completed.
+ *
+ *
+ * JobGroups maintain state for the collective status of the jobs belonging to the group.
+ * When constructed, a job group starts with a state value of NONE.
+ * When any job belonging to the group is scheduled to run, the job group moves into the
+ * ACTIVE state. A job group enters the CANCELING state
+ * when cancellation of the whole group is requested. The group will be in this state
+ * until all the jobs in the group have finished either through cancellation or
+ * normal completion. When a job group is in the CANCELING state,
+ * newly scheduled jobs which are part of that group are immediately canceled.
+ * When execution of all the jobs belonging to a job group finishes (either normally
+ * or through cancellation), the job group state changes back to NONE.
+ *
+ * @see IJobManager
+ * @since 3.7
+ */
+public class JobGroup extends InternalJobGroup {
+ /**
+ * JobGroup state code (value 0) indicating that none of the jobs belonging to
+ * the group are running or scheduled to run.
+ *
+ * @see #getState()
+ */
+ public static final int NONE = 0;
+ /**
+ * JobGroup state code (value 1) indicating that at least one job belonging to
+ * the group is running or scheduled to run.
+ *
+ * @see #getState()
+ */
+ public static final int ACTIVE = 0x01;
+ /**
+ * JobGroup state code (value 2) indicating that cancellation of the whole group is requested.
+ * The group will be in this state until all the jobs in the group have finished either through
+ * cancellation or normal completion. When a job group is in this state, newly scheduled jobs
+ * which are part of that group are immediately canceled.
+ */
+ public static final int CANCELING = 0x02;
+
+ /**
+ * Creates a new job group with the specified name and maxThreads.
+ * The job group name is a human-readable value that is displayed to users. The name does
+ * not need to be unique, but it must not be null. The maxThreads
+ * indicates the maximum number of threads allowed to be concurrently scheduled by the
+ * jobs belonging to the group at any given time, or zero to indicate that
+ * no throttling should be applied and all jobs should be allowed to run as soon as possible.
+ *
+ * @param name the name of the job group.
+ * @param maxThreads the maximum number of threads allowed to be concurrently scheduled,
+ * or zero to indicate that no throttling should be applied and all jobs
+ * should be allowed to run as soon as possible.
+ * @param seedJobsCount the initial number of jobs that will be added to the job group.
+ * This is the initial count of jobs with which the creator of the job group will "seed"
+ * the job group. Those initial jobs may discover more work and add yet more jobs, but
+ * those additional jobs should not be included in this initial "seed" count. If this
+ * value is set too high, the job group will never transition to the done ({@link #NONE})
+ * state, {@link #join(long, IProgressMonitor)} calls will hang, and {@link #getResult()}
+ * calls will return invalid results. If this value is set too low, the job group may
+ * transition to the ({@link #NONE}) state before all of the jobs have been scheduled,
+ * causing a {@link #join(long, IProgressMonitor)} call to return too early.
+ */
+ public JobGroup(String name, int maxThreads, int seedJobsCount) {
+ super(name, maxThreads, seedJobsCount);
+ }
+
+ /**
+ * Returns the human readable name of this job group. The name is never null.
+ *
+ * @return the name of this job group
+ */
+ @Override
+ public final String getName() {
+ return super.getName();
+ }
+
+ /**
+ * Returns the maximum number of threads allowed to be scheduled by the jobs belonging
+ * to the group at any given time, or zero to indicate that no throttling
+ * should be applied and all jobs should be allowed to run as soon as possible.
+ *
+ * @return the maximum number of threads allowed to be used.
+ */
+ @Override
+ public final int getMaxThreads() {
+ return super.getMaxThreads();
+ }
+
+ /**
+ * Returns the result of this job group's last run. If a job group completes and then
+ * its jobs are rescheduled, this method returns the results of the previous run.
+ *
+ * @return the result of this job group's last run, or null if this
+ * job group has never finished running.
+ */
+ @Override
+ public final MultiStatus getResult() {
+ return super.getResult();
+ }
+
+ /**
+ * Returns the state of the job group. Result will be one of:
+ *
+ *
JobGroup.NONE - when the jobs belonging to the group are not yet scheduled to run.
+ *
JobGroup.ACTIVE - when the jobs belonging to the group are running or scheduled to run.
+ *
JobGroup.CANCELING - when the jobs belonging to the group are being canceled.
+ *
+ *
+ * Note that job group state is inherently volatile, and in most cases clients cannot rely
+ * on the result of this method being valid by the time the result is obtained. For example,
+ * if getState returns ACTIVE, the job group may have actually completed
+ * by the time the getState method returns. All that clients can infer from invoking
+ * this method is that the job group was recently in the returned state.
+ *
+ * @return the job group state
+ */
+ @Override
+ public final int getState() {
+ return super.getState();
+ }
+
+ /**
+ * Returns all waiting, executing and sleeping jobs belonging
+ * to this job group. If no jobs are found, an empty array is returned.
+ *
+ * @return the list of active jobs
+ * @see Job#setJobGroup(JobGroup)
+ */
+ @Override
+ public final List getActiveJobs() {
+ return super.getActiveJobs();
+ }
+
+ /**
+ * Cancels all jobs belonging to this job group. Jobs belonging to this group
+ * that are currently in the WAITING state will be removed from the queue.
+ * Sleeping jobs will be discarded without having a chance to wake up.
+ * Currently executing jobs will be asked to cancel but there is no guarantee
+ * that they will do so.
+ *
+ * The job group will be placed into CANCELING state and will be
+ * in that state until all the jobs in the group have finished either through
+ * cancellation or normal completion. When a job group is in the CANCELING
+ * state, newly scheduled jobs which are part of the group are immediately canceled.
+ *
+ * @see Job#setJobGroup(JobGroup)
+ * @see JobGroup#getState()
+ */
+ @Override
+ public final void cancel() {
+ super.cancel();
+ }
+
+ /**
+ * Waits until either all jobs belonging to this job group have finished or
+ * the given timeout has expired. This method will block the calling thread
+ * until all such jobs have finished executing, or the given timeout is expired,
+ * or the given progress monitor is canceled by the user or the calling thread
+ * is interrupted. If there are no jobs belonging to the group that are currently
+ * waiting, running, or sleeping, this method returns immediately. Feedback
+ * on how the join is progressing is provided to the given progress monitor.
+ *
+ * If this method is called while the job manager is suspended, only jobs
+ * that are currently running will be joined. Once there are no jobs
+ * belongs to the group in the {@link Job#RUNNING} state, the method returns.
+ *
+ *
+ * Jobs may be added to this job group after the initial set of jobs are scheduled,
+ * and this method will wait for all newly added jobs to complete (or the given timeout
+ * has expired), even if they are added to the group after this method is invoked.
+ *
+ *
+ * Throws an OperationCanceledException when the given progress monitor
+ * is canceled. Canceling the monitor does not cancel the jobs belonging to the group and,
+ * if required, the group may be canceled explicitly using the {@link #cancel()} method.
+ *
+ *
+ * @param timeoutMillis the maximum amount of time to wait for the join to complete,
+ * or zero for no timeout.
+ * @param monitor the progress monitor for reporting progress on how the wait is
+ * progressing and to be able to cancel the join operation, or null
+ * if no progress monitoring is required.
+ * @return true when all the jobs in the group complete,
+ * or false when the operation is not completed within the given time.
+ * @exception InterruptedException if the calling thread is interrupted while waiting
+ * @exception OperationCanceledException if the progress monitor is canceled while waiting
+ * @see Job#setJobGroup(JobGroup)
+ * @see #cancel()
+ */
+ @Override
+ public final boolean join(long timeoutMillis, IProgressMonitor monitor) throws InterruptedException, OperationCanceledException {
+ return super.join(timeoutMillis, monitor);
+ }
+
+ /**
+ * This method is called by the JobManager after the completion of every job belonging
+ * to this group, and is used to control the job group's cancellation policy. Returning
+ * true from this function causes all remaining running and scheduled jobs
+ * to be canceled.
+ *
+ * The default implementation returns true when numberOfFailedJobs > 0.
+ * Subclasses may override this method to implement a different cancellation strategy.
+ *
+ * @param lastCompletedJobResult result of the last completed job belonging to this group.
+ * @param numberOfFailedJobs the total number of jobs belonging to this group that are
+ * finished with status IStatus.ERROR.
+ * @param numberOfCanceledJobs the total number of jobs belonging to this group that are
+ * finished with status IStatus.CANCEL.
+ * @return true when the remaining jobs belonging to this group should be canceled.
+ */
+ @Override
+ protected boolean shouldCancel(IStatus lastCompletedJobResult, int numberOfFailedJobs, int numberOfCanceledJobs) {
+ return super.shouldCancel(lastCompletedJobResult, numberOfFailedJobs, numberOfCanceledJobs);
+ }
+
+ /**
+ * This method is called by the JobManager when all the jobs belonging to the group
+ * are completed. The combined status returned by this method is used as the result
+ * of the job group.
+ *
+ * The default implementation will return a MultiStatus object containing
+ * the returned statuses of the completed jobs. The results with IStatus.OK
+ * are omitted from the result since those statuses usually do not contain valuable information.
+ * Subclasses may override this method to implement custom status reporting,
+ * but should never return null.
+ *
+ * @param jobResults results of all the completed jobs belonging to this group.
+ * @return the combined status of the group, not null.
+ * @see #getResult()
+ */
+ @Override
+ protected MultiStatus computeGroupResult(List jobResults) {
+ return super.computeGroupResult(jobResults);
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/LockListener.java b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/LockListener.java
new file mode 100644
index 0000000000..0de46b2d2c
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/LockListener.java
@@ -0,0 +1,77 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2012 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM - Initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.runtime.jobs;
+
+import org.eclipse.core.internal.jobs.JobManager;
+import org.eclipse.core.internal.jobs.LockManager;
+
+/**
+ * A lock listener is notified whenever a thread is about to wait
+ * on a lock, and when a thread is about to release a lock.
+ *
+ * This class is for internal use by the platform-related plug-ins.
+ * Clients outside of the base platform should not reference or subclass this class.
+ *
+ *
+ * @see IJobManager#setLockListener(LockListener)
+ * @since 3.0
+ */
+public class LockListener {
+ private final LockManager manager = ((JobManager) Job.getJobManager()).getLockManager();
+
+ /**
+ * Notification that a thread is about to block on an attempt to acquire a lock.
+ * Returns whether the thread should be granted immediate access to the lock.
+ *
+ * This default implementation always returns false.
+ * Subclasses may override.
+ *
+ * @param lockOwner the thread that currently owns the lock this thread is
+ * waiting for, or null if unknown.
+ * @return true if the thread should be granted immediate access,
+ * and false if it should wait for the lock to be available
+ */
+ public boolean aboutToWait(Thread lockOwner) {
+ return false;
+ }
+
+ /**
+ * Notification that a thread is about to release a lock.
+ *
+ * This default implementation does nothing. Subclasses may override.
+ */
+ public void aboutToRelease() {
+ //do nothing
+ }
+
+ /**
+ * Returns if it is safe for the calling thread to block while waiting to obtain
+ * a lock. When blocking in the calling thread is not safe, the caller will ensure
+ * that the thread is kept alive and responsive to cancellation while waiting.
+ *
+ * @return true if this thread can block, and
+ * false otherwise.
+ *
+ * @since org.eclipse.core.jobs 3.5
+ */
+ public boolean canBlock() {
+ return true;
+ }
+
+ /**
+ * Returns whether this thread currently owns any locks
+ * @return true if this thread owns any locks, and
+ * false otherwise.
+ */
+ protected final boolean isLockOwnerThread() {
+ return manager.isLockOwner();
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/MultiRule.java b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/MultiRule.java
new file mode 100644
index 0000000000..7175c33851
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/MultiRule.java
@@ -0,0 +1,184 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2012 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM - Initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.runtime.jobs;
+
+import java.util.ArrayList;
+
+/**
+ * A MultiRule is a compound scheduling rule that represents a fixed group of child
+ * scheduling rules. A MultiRule conflicts with another rule if any of its children conflict
+ * with that rule. More formally, a compound rule represents a logical intersection
+ * of its child rules with respect to the isConflicting equivalence
+ * relation.
+ *
+ * A MultiRule will never contain other MultiRules as children. If a MultiRule is provided
+ * as a child, its children will be added instead.
+ *
+ *
+ * @since 3.0
+ * @noextend This class is not intended to be subclassed by clients.
+ */
+public class MultiRule implements ISchedulingRule {
+ private ISchedulingRule[] rules;
+
+ /**
+ * Returns a scheduling rule that encompasses all provided rules. The resulting
+ * rule may or may not be an instance of MultiRule. If all
+ * provided rules are null then the result will be
+ * null.
+ *
+ * @param ruleArray An array of scheduling rules, some of which may be null
+ * @return a combined scheduling rule, or null
+ * @since 3.1
+ */
+ public static ISchedulingRule combine(ISchedulingRule[] ruleArray) {
+ ISchedulingRule result = null;
+ for (int i = 0; i < ruleArray.length; i++) {
+ if (ruleArray[i] == null)
+ continue;
+ if (result == null) {
+ result = ruleArray[i];
+ continue;
+ }
+ result = combine(result, ruleArray[i]);
+ }
+ return result;
+ }
+
+ /**
+ * Returns a scheduling rule that encompasses both provided rules. The resulting
+ * rule may or may not be an instance of MultiRule. If both
+ * provided rules are null then the result will be
+ * null.
+ *
+ * @param rule1 a scheduling rule, or null
+ * @param rule2 another scheduling rule, or null
+ * @return a combined scheduling rule, or null
+ */
+ public static ISchedulingRule combine(ISchedulingRule rule1, ISchedulingRule rule2) {
+ if (rule1 == rule2)
+ return rule1;
+ if (rule1 == null)
+ return rule2;
+ if (rule2 == null)
+ return rule1;
+ if (rule1.contains(rule2))
+ return rule1;
+ if (rule2.contains(rule1))
+ return rule2;
+ MultiRule result = new MultiRule();
+ result.rules = new ISchedulingRule[] {rule1, rule2};
+ //make sure we don't end up with nested multi-rules
+ if (rule1 instanceof MultiRule || rule2 instanceof MultiRule)
+ result.rules = flatten(result.rules);
+ return result;
+ }
+
+ /*
+ * Collapses an array of rules that may contain MultiRules into an
+ * array in which no rules are MultiRules.
+ */
+ private static ISchedulingRule[] flatten(ISchedulingRule[] nestedRules) {
+ ArrayList myRules = new ArrayList(nestedRules.length);
+ for (int i = 0; i < nestedRules.length; i++) {
+ if (nestedRules[i] instanceof MultiRule) {
+ ISchedulingRule[] children = ((MultiRule) nestedRules[i]).getChildren();
+ for (int j = 0; j < children.length; j++)
+ myRules.add(children[j]);
+ } else {
+ myRules.add(nestedRules[i]);
+ }
+ }
+ return myRules.toArray(new ISchedulingRule[myRules.size()]);
+ }
+
+ /**
+ * Creates a new scheduling rule that composes a set of nested rules.
+ *
+ * @param nestedRules the nested rules for this compound rule.
+ */
+ public MultiRule(ISchedulingRule[] nestedRules) {
+ this.rules = flatten(nestedRules);
+ }
+
+ /**
+ * Creates a new scheduling rule with no nested rules. For
+ * internal use only.
+ */
+ private MultiRule() {
+ //to be invoked only by factory methods
+ }
+
+ /**
+ * Returns the child rules within this rule.
+ * @return the child rules
+ */
+ public ISchedulingRule[] getChildren() {
+ return rules.clone();
+ }
+
+ @Override
+ public boolean contains(ISchedulingRule rule) {
+ if (this == rule)
+ return true;
+ if (rule instanceof MultiRule) {
+ ISchedulingRule[] otherRules = ((MultiRule) rule).getChildren();
+ //for each child of the target, there must be some child in this rule that contains it.
+ for (int other = 0; other < otherRules.length; other++) {
+ boolean found = false;
+ for (int mine = 0; !found && mine < rules.length; mine++)
+ found = rules[mine].contains(otherRules[other]);
+ if (!found)
+ return false;
+ }
+ return true;
+ }
+ for (int i = 0; i < rules.length; i++)
+ if (rules[i].contains(rule))
+ return true;
+ return false;
+ }
+
+ @Override
+ public boolean isConflicting(ISchedulingRule rule) {
+ if (this == rule)
+ return true;
+ if (rule instanceof MultiRule) {
+ ISchedulingRule[] otherRules = ((MultiRule) rule).getChildren();
+ for (int j = 0; j < otherRules.length; j++)
+ for (int i = 0; i < rules.length; i++)
+ if (rules[i].isConflicting(otherRules[j]))
+ return true;
+ } else {
+ for (int i = 0; i < rules.length; i++)
+ if (rules[i].isConflicting(rule))
+ return true;
+ }
+ return false;
+ }
+
+ /*
+ * For debugging purposes only.
+ */
+ @Override
+ public String toString() {
+ StringBuffer buffer = new StringBuffer();
+ buffer.append("MultiRule["); //$NON-NLS-1$
+ int last = rules.length - 1;
+ for (int i = 0; i < rules.length; i++) {
+ buffer.append(rules[i]);
+ if (i != last)
+ buffer.append(',');
+ }
+ buffer.append(']');
+ return buffer.toString();
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/ProgressProvider.java b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/ProgressProvider.java
new file mode 100644
index 0000000000..ff30844fa0
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/ProgressProvider.java
@@ -0,0 +1,98 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2012 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.runtime.jobs;
+
+import org.eclipse.core.runtime.*;
+
+/**
+ * The progress provider supplies the job manager with progress monitors for
+ * running jobs. There can only be one progress provider at any given time.
+ *
+ * This class is intended for use by the currently executing Eclipse application.
+ * Plug-ins outside the currently running application should not reference or
+ * subclass this class.
+ *
+ *
+ * @see IJobManager#setProgressProvider(ProgressProvider)
+ * @since 3.0
+ */
+public abstract class ProgressProvider {
+ /**
+ * Provides a new progress monitor instance to be used by the given job.
+ * This method is called prior to running any job that does not belong to a
+ * progress group. The returned monitor will be supplied to the job's
+ * run method.
+ *
+ * @see #createProgressGroup()
+ * @see Job#setProgressGroup(IProgressMonitor, int)
+ * @param job the job to create a progress monitor for
+ * @return a progress monitor, or null if no progress monitoring
+ * is needed.
+ */
+ public abstract IProgressMonitor createMonitor(Job job);
+
+ /**
+ * Returns a progress monitor that can be used to provide
+ * aggregated progress feedback on a set of running jobs.
+ * This method implements IJobManager.createProgressGroup,
+ * and must obey all rules specified in that contract.
+ *
+ * This default implementation returns a new
+ * NullProgressMonitor Subclasses may override.
+ *
+ * @see IJobManager#createProgressGroup()
+ * @return a progress monitor
+ */
+ public IProgressMonitor createProgressGroup() {
+ return new NullProgressMonitor();
+ }
+
+ /**
+ * Returns a progress monitor that can be used by a running job
+ * to report progress in the context of a progress group. This method
+ * implements Job.setProgressGroup. One of the
+ * two createMonitor methods will be invoked
+ * prior to each execution of a job, depending on whether a progress
+ * group was specified for the job.
+ *
+ * The provided monitor must be a monitor returned by the method
+ * createProgressGroup. This method is responsible
+ * for asserting this and throwing an appropriate runtime exception
+ * if an invalid monitor is provided.
+ *
+ * This default implementation returns a new
+ * SubProgressMonitor. Subclasses may override.
+ *
+ * @see IJobManager#createProgressGroup()
+ * @see Job#setProgressGroup(IProgressMonitor, int)
+ * @param job the job to create a progress monitor for
+ * @param group the progress monitor group that this job belongs to
+ * @param ticks the number of ticks of work for the progress monitor
+ * @return a progress monitor, or null if no progress monitoring
+ * is needed.
+ */
+ public IProgressMonitor createMonitor(Job job, IProgressMonitor group, int ticks) {
+ return new SubProgressMonitor(group, ticks);
+ }
+
+ /**
+ * Returns a progress monitor to use when none has been provided
+ * by the client running the job.
+ *
+ * This default implementation returns a new
+ * NullProgressMonitor Subclasses may override.
+ *
+ * @return a progress monitor
+ */
+ public IProgressMonitor getDefaultMonitor() {
+ return new NullProgressMonitor();
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/package.html b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/package.html
new file mode 100644
index 0000000000..1e6256b088
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/package.html
@@ -0,0 +1,22 @@
+
+
+
+
+ Package-level Javadoc
+
+
+Provides core support for scheduling and interacting with background activity.
+
+Package Specification
+
+This package specifies API for scheduling background tasks, or jobs. Jobs can be
+scheduled for immediate execution, or for execution after a specified delay. Once
+scheduled, jobs can be queried, canceled, or suspended. Rules can be attached to
+jobs to indicate when they can run, and whether they can run simultaneously with other
+jobs. This package also includes a generic locking facility that includes support for
+detecting and responding to deadlock.
+
The Eclipse Foundation makes available all content in this plug-in ("Content"). Unless otherwise
+indicated below, the Content is provided to you under the terms and conditions of the
+Eclipse Public License Version 1.0 ("EPL"). A copy of the EPL is available
+at http://www.eclipse.org/legal/epl-v10.html.
+For purposes of the EPL, "Program" will mean the Content.
+
+
If you did not receive this Content directly from the Eclipse Foundation, the Content is
+being redistributed by another party ("Redistributor") and different terms and conditions may
+apply to your use of any object code in the Content. Check the Redistributor's license that was
+provided with the Content. If no such license exists, contact the Redistributor. Unless otherwise
+indicated below, the terms and conditions of the EPL still apply to any source code in the Content
+and such source code may be obtained at http://www.eclipse.org.
+
+
+
\ No newline at end of file
diff --git a/third_party/patches/mars/org.eclipse.core.resources/ant_tasks/resources-antsrc/META-INF/eclipse.inf b/third_party/patches/mars/org.eclipse.core.resources/ant_tasks/resources-antsrc/META-INF/eclipse.inf
new file mode 100644
index 0000000000..16227e42fc
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/ant_tasks/resources-antsrc/META-INF/eclipse.inf
@@ -0,0 +1,2 @@
+jarprocessor.exclude.sign=true
+jarprocessor.exclude.pack=true
\ No newline at end of file
diff --git a/third_party/patches/mars/org.eclipse.core.resources/ant_tasks/resources-antsrc/org/eclipse/core/resources/ant/ConvertPath.java b/third_party/patches/mars/org.eclipse.core.resources/ant_tasks/resources-antsrc/org/eclipse/core/resources/ant/ConvertPath.java
new file mode 100644
index 0000000000..d3df18fa68
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/ant_tasks/resources-antsrc/org/eclipse/core/resources/ant/ConvertPath.java
@@ -0,0 +1,176 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2014 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.resources.ant;
+
+import java.io.File;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Task;
+import org.apache.tools.ant.types.Path;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Platform;
+
+/**
+ * An Ant task which allows to switch from a file system path to a resource path,
+ * and vice versa, and store the result in a user property whose name is set by the user. If the
+ * resource does not exist, the property is set to false.
+ *
+ * The attribute "property" must be specified, as well as only one of "fileSystemPath" or "resourcePath".
+ *
+ * Example:
+ * <eclipse.convertPath fileSystemPath="D:\MyWork\MyProject" property="myProject.resourcePath"/>
+ */
+public class ConvertPath extends Task {
+
+ /**
+ * The file system path.
+ */
+ private IPath fileSystemPath = null;
+
+ /**
+ * The resource path.
+ */
+ private IPath resourcePath = null;
+
+ /**
+ * The name of the property where the result may be stored.
+ */
+ private String property = null;
+
+ /**
+ * The id of the new Path object that may be created.
+ */
+ private String pathID = null;
+
+ /**
+ * Constructs a new ConvertPath instance.
+ */
+ public ConvertPath() {
+ super();
+ }
+
+ /**
+ * Performs the path conversion operation.
+ *
+ * @exception BuildException thrown if a problem occurs during execution.
+ */
+ @Override
+ public void execute() throws BuildException {
+ validateAttributes();
+ if (fileSystemPath == null)
+ // here, resourcePath is not null
+ convertResourcePathToFileSystemPath(resourcePath);
+ else
+ convertFileSystemPathToResourcePath(fileSystemPath);
+ }
+
+ protected void convertFileSystemPathToResourcePath(IPath path) {
+ IResource resource;
+ if (Platform.getLocation().equals(path)) {
+ resource = ResourcesPlugin.getWorkspace().getRoot();
+ } else {
+ resource = ResourcesPlugin.getWorkspace().getRoot().getContainerForLocation(path);
+ if (resource == null)
+ throw new BuildException(Policy.bind("exception.noProjectMatchThePath", fileSystemPath.toOSString())); //$NON-NLS-1$
+ }
+ if (property != null)
+ getProject().setUserProperty(property, resource.getFullPath().toString());
+ if (pathID != null) {
+ Path newPath = new Path(getProject(), resource.getFullPath().toString());
+ getProject().addReference(pathID, newPath);
+ }
+ }
+
+ protected void convertResourcePathToFileSystemPath(IPath path) {
+ IResource resource = null;
+ switch (path.segmentCount()) {
+ case 0 :
+ resource = ResourcesPlugin.getWorkspace().getRoot();
+ break;
+ case 1 :
+ resource = ResourcesPlugin.getWorkspace().getRoot().getProject(path.lastSegment());
+ break;
+ default :
+ resource = ResourcesPlugin.getWorkspace().getRoot().getFile(path);
+ }
+
+ if (resource.getLocation() == null)
+ // can occur if the first segment is not a project
+ throw new BuildException(Policy.bind("exception.pathNotValid", path.toString())); //$NON-NLS-1$
+
+ if (property != null)
+ getProject().setUserProperty(property, resource.getLocation().toOSString());
+ if (pathID != null) {
+ Path newPath = new Path(getProject(), resource.getLocation().toOSString());
+ getProject().addReference(pathID, newPath);
+ }
+ }
+
+ /**
+ * Sets the file system path.
+ *
+ * @param value the file corresponding to the path supplied by the user
+ */
+ public void setFileSystemPath(File value) {
+ if (resourcePath != null)
+ throw new BuildException(Policy.bind("exception.cantUseBoth")); //$NON-NLS-1$
+ fileSystemPath = new org.eclipse.core.runtime.Path(value.toString());
+ }
+
+ /**
+ * Sets the resource path.
+ *
+ * @param value the path
+ */
+ public void setResourcePath(String value) {
+ if (fileSystemPath != null)
+ throw new BuildException(Policy.bind("exception.cantUseBoth")); //$NON-NLS-1$
+ resourcePath = new org.eclipse.core.runtime.Path(value);
+ }
+
+ /**
+ * Sets the name of the property where the result may stored.
+ *
+ * @param value the name of the property
+ */
+ public void setProperty(String value) {
+ property = value;
+
+ }
+
+ /**
+ * Sets the id for the path where the result may be stored
+ *
+ * @param value the id of the path
+ */
+ public void setPathId(String value) {
+ pathID = value;
+ }
+
+ /**
+ * Performs a validation of the receiver.
+ *
+ * @exception BuildException thrown if a problem occurs during validation.
+ */
+ protected void validateAttributes() throws BuildException {
+ if (property == null && pathID == null)
+ throw new BuildException(Policy.bind("exception.propertyAndPathIdNotSpecified")); //$NON-NLS-1$
+
+ if (resourcePath != null && (!resourcePath.isValidPath(resourcePath.toString()) || resourcePath.isEmpty()))
+ throw new BuildException(Policy.bind("exception.invalidPath", resourcePath.toOSString())); //$NON-NLS-1$
+ else if (fileSystemPath != null && !fileSystemPath.isValidPath(fileSystemPath.toOSString()))
+ throw new BuildException(Policy.bind("exception.invalidPath", fileSystemPath.toOSString())); //$NON-NLS-1$
+
+ if (resourcePath == null && fileSystemPath == null)
+ throw new BuildException(Policy.bind("exception.mustHaveOneAttribute")); //$NON-NLS-1$
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/ant_tasks/resources-antsrc/org/eclipse/core/resources/ant/IncrementalBuild.java b/third_party/patches/mars/org.eclipse.core.resources/ant_tasks/resources-antsrc/org/eclipse/core/resources/ant/IncrementalBuild.java
new file mode 100644
index 0000000000..417a6b3848
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/ant_tasks/resources-antsrc/org/eclipse/core/resources/ant/IncrementalBuild.java
@@ -0,0 +1,126 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2014 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.resources.ant;
+
+import java.util.Hashtable;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Task;
+import org.eclipse.ant.core.AntCorePlugin;
+import org.eclipse.core.resources.*;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+
+/**
+ * Ant task which runs the platform's incremental build facilities.
+ *
+ * @see IProject#build(int, IProgressMonitor)
+ * @see IWorkspace#build(int, IProgressMonitor)
+ */
+public class IncrementalBuild extends Task {
+ private String builder;
+ private String project;
+ private int kind = IncrementalProjectBuilder.INCREMENTAL_BUILD;
+
+ /**
+ * Unique identifier constant (value "incremental")
+ * indicating that an incremental build should be performed.
+ */
+ public final static String KIND_INCREMENTAL = "incremental"; //$NON-NLS-1$
+
+ /**
+ * Unique identifier constant (value "full")
+ * indicating that a full build should be performed.
+ */
+ public final static String KIND_FULL = "full"; //$NON-NLS-1$
+
+ /**
+ * Unique identifier constant (value "auto")
+ * indicating that an auto build should be performed.
+ */
+ public final static String KIND_AUTO = "auto"; //$NON-NLS-1$
+
+ /**
+ * Unique identifier constant (value "clean")
+ * indicating that a CLEAN build should be performed.
+ */
+ public final static String KIND_CLEAN = "clean"; //$NON-NLS-1$
+
+ /**
+ * Constructs an IncrementalBuild instance.
+ */
+ public IncrementalBuild() {
+ super();
+ }
+
+ /**
+ * Executes this task.
+ *
+ * @exception BuildException thrown if a problem occurs during execution
+ */
+ @Override
+ public void execute() throws BuildException {
+ try {
+ IProgressMonitor monitor = null;
+ Hashtable references = getProject().getReferences();
+ if (references != null)
+ monitor = (IProgressMonitor) references.get(AntCorePlugin.ECLIPSE_PROGRESS_MONITOR);
+ if (project == null) {
+ ResourcesPlugin.getWorkspace().build(kind, monitor);
+ } else {
+ IProject targetProject = ResourcesPlugin.getWorkspace().getRoot().getProject(project);
+ if (builder == null)
+ targetProject.build(kind, monitor);
+ else
+ targetProject.build(kind, builder, null, monitor);
+ }
+ } catch (CoreException e) {
+ throw new BuildException(e);
+ }
+ }
+
+ /**
+ * Sets the name of the receiver's builder.
+ *
+ * @param value the name of the receiver's builder
+ */
+ public void setBuilder(String value) {
+ builder = value;
+ }
+
+ /**
+ * Sets the receiver's kind> attribute. This value must be one
+ * of: IncrementalBuild.KIND_FULL,
+ * IncrementalBuild.KIND_AUTO,
+ * IncrementalBuild.KIND_INCREMENTAL,
+ * IncrementalBuild.KIND_CLEAN.
+ *
+ * @param value the receiver's kind attribute
+ */
+ public void setKind(String value) {
+ if (IncrementalBuild.KIND_FULL.equalsIgnoreCase(value))
+ kind = IncrementalProjectBuilder.FULL_BUILD;
+ else if (IncrementalBuild.KIND_AUTO.equalsIgnoreCase(value))
+ kind = IncrementalProjectBuilder.AUTO_BUILD;
+ else if (IncrementalBuild.KIND_CLEAN.equalsIgnoreCase(value))
+ kind = IncrementalProjectBuilder.CLEAN_BUILD;
+ else if (IncrementalBuild.KIND_INCREMENTAL.equalsIgnoreCase(value))
+ kind = IncrementalProjectBuilder.INCREMENTAL_BUILD;
+ }
+
+ /**
+ * Sets the receiver's target project.
+ *
+ * @param value the receiver's target project
+ */
+ public void setProject(String value) {
+ project = value;
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/ant_tasks/resources-antsrc/org/eclipse/core/resources/ant/Policy.java b/third_party/patches/mars/org.eclipse.core.resources/ant_tasks/resources-antsrc/org/eclipse/core/resources/ant/Policy.java
new file mode 100644
index 0000000000..e92fb61dbc
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/ant_tasks/resources-antsrc/org/eclipse/core/resources/ant/Policy.java
@@ -0,0 +1,71 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2012 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.resources.ant;
+
+import java.text.MessageFormat;
+import java.util.*;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.NullProgressMonitor;
+// can't use ICU, used by ant
+
+public class Policy {
+ private static final String bundleName = "org.eclipse.core.resources.ant.messages";//$NON-NLS-1$
+ private static ResourceBundle bundle = ResourceBundle.getBundle(bundleName, Locale.getDefault());
+
+ /**
+ * Lookup the message with the given ID in this catalog
+ */
+ public static String bind(String id) {
+ return bind(id, (String[]) null);
+ }
+
+ public static IProgressMonitor monitorFor(IProgressMonitor monitor) {
+ if (monitor == null)
+ return new NullProgressMonitor();
+ return monitor;
+ }
+
+ /**
+ * Lookup the message with the given ID in this catalog and bind its
+ * substitution locations with the given string.
+ */
+ public static String bind(String id, String binding) {
+ return bind(id, new String[] {binding});
+ }
+
+ /**
+ * Lookup the message with the given ID in this catalog and bind its
+ * substitution locations with the given strings.
+ */
+ public static String bind(String id, String binding1, String binding2) {
+ return bind(id, new String[] {binding1, binding2});
+ }
+
+ /**
+ * Lookup the message with the given ID in this catalog and bind its
+ * substitution locations with the given string values.
+ */
+ public static String bind(String id, String[] bindings) {
+ if (id == null)
+ return "No message available";//$NON-NLS-1$
+ String message = null;
+ try {
+ message = bundle.getString(id);
+ } catch (MissingResourceException e) {
+ // If we got an exception looking for the message, fail gracefully by just returning
+ // the id we were looking for. In most cases this is semi-informative so is not too bad.
+ return "Missing message: " + id + " in: " + bundleName;//$NON-NLS-1$ //$NON-NLS-2$
+ }
+ if (bindings == null)
+ return message;
+ return MessageFormat.format(message, (Object[]) bindings);
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/ant_tasks/resources-antsrc/org/eclipse/core/resources/ant/RefreshLocalTask.java b/third_party/patches/mars/org.eclipse.core.resources/ant_tasks/resources-antsrc/org/eclipse/core/resources/ant/RefreshLocalTask.java
new file mode 100644
index 0000000000..d0c1bddc1e
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/ant_tasks/resources-antsrc/org/eclipse/core/resources/ant/RefreshLocalTask.java
@@ -0,0 +1,120 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2014 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.resources.ant;
+
+import java.util.Hashtable;
+import org.apache.tools.ant.*;
+import org.eclipse.ant.core.AntCorePlugin;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.*;
+
+/**
+ * An Ant task which refreshes the Eclipse Platform's view of the local filesystem.
+ *
+ * @see IResource#refreshLocal(int, IProgressMonitor)
+ */
+public class RefreshLocalTask extends Task {
+ /**
+ * Unique identifier constant (value "DEPTH_ZERO")
+ * indicating that refreshes should be performed only on the target
+ * resource itself
+ */
+ public static final String DEPTH_ZERO = "zero"; //$NON-NLS-1$
+
+ /**
+ * Unique identifier constant (value "DEPTH_ONE")
+ * indicating that refreshes should be performed on the target
+ * resource and its children
+ */
+ public static final String DEPTH_ONE = "one"; //$NON-NLS-1$
+
+ /**
+ * Unique identifier constant (value "DEPTH_INFINITE")
+ * indicating that refreshes should be performed on the target
+ * resource and all of its recursive children
+ */
+ public static final String DEPTH_INFINITE = "infinite"; //$NON-NLS-1$
+
+ /**
+ * The resource to refresh.
+ */
+ protected IResource resource;
+
+ /**
+ * The depth to refresh to.
+ */
+ protected int depth = IResource.DEPTH_INFINITE;
+
+ /**
+ * Constructs a new RefreshLocal instance.
+ */
+ public RefreshLocalTask() {
+ super();
+ }
+
+ /**
+ * Performs the refresh operation.
+ *
+ * @exception BuildException thrown if a problem occurs during execution.
+ */
+ @Override
+ public void execute() throws BuildException {
+ if (resource == null)
+ throw new BuildException(Policy.bind("exception.resourceNotSpecified")); //$NON-NLS-1$
+ try {
+ IProgressMonitor monitor = null;
+ Hashtable references = getProject().getReferences();
+ if (references != null)
+ monitor = (IProgressMonitor) references.get(AntCorePlugin.ECLIPSE_PROGRESS_MONITOR);
+ resource.refreshLocal(depth, monitor);
+ } catch (CoreException e) {
+ throw new BuildException(e);
+ }
+ }
+
+ /**
+ * Sets the depth of this task appropriately. The specified argument must
+ * by one of RefreshLocal.DEPTH_ZERO, RefreshLocal.DEPTH_ONE
+ * or RefreshLocal.DEPTH_INFINITE.
+ *
+ * @param value the depth to refresh to
+ */
+ public void setDepth(String value) {
+ if (DEPTH_ZERO.equalsIgnoreCase(value))
+ depth = IResource.DEPTH_ZERO;
+ else if (DEPTH_ONE.equalsIgnoreCase(value))
+ depth = IResource.DEPTH_ONE;
+ else if (DEPTH_INFINITE.equalsIgnoreCase(value))
+ depth = IResource.DEPTH_INFINITE;
+ }
+
+ /**
+ * Sets the root of the workspace resource tree to refresh.
+ *
+ * @param value the root value
+ */
+ public void setResource(String value) {
+ IPath path = new Path(value);
+ resource = ResourcesPlugin.getWorkspace().getRoot().findMember(path);
+ if (resource == null) {
+ // if it does not exist we guess it is a folder or a project
+ if (path.segmentCount() > 1)
+ resource = ResourcesPlugin.getWorkspace().getRoot().getFolder(path);
+ else {
+ resource = ResourcesPlugin.getWorkspace().getRoot().getProject(value);
+ if (!resource.exists())
+ log(Policy.bind("warning.projectDoesNotExist", value), Project.MSG_WARN); //$NON-NLS-1$
+ }
+ }
+ }
+
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/ant_tasks/resources-antsrc/org/eclipse/core/resources/ant/messages.properties b/third_party/patches/mars/org.eclipse.core.resources/ant_tasks/resources-antsrc/org/eclipse/core/resources/ant/messages.properties
new file mode 100644
index 0000000000..b84e3aec4e
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/ant_tasks/resources-antsrc/org/eclipse/core/resources/ant/messages.properties
@@ -0,0 +1,22 @@
+###############################################################################
+# Copyright (c) 2000, 2005 IBM Corporation and others.
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Eclipse Public License v1.0
+# which accompanies this distribution, and is available at
+# http://www.eclipse.org/legal/epl-v10.html
+#
+# Contributors:
+# IBM Corporation - initial API and implementation
+###############################################################################
+### resources-ant.jar messages
+
+
+
+exception.cantUseBoth="fileSystemPath" and "resourcePath" can't be used at the same time.
+exception.invalidPath=Invalid path: {0}.
+exception.mustHaveOneAttribute="fileSystemPath" or "resourcePath" must be specified.
+exception.noProjectMatchThePath=The path {0} does not match any existing project.
+exception.pathNotValid=The path {0} for the resource is not a valid path as the first segment does not represent a project.
+exception.propertyAndPathIdNotSpecified=At least one of the "property" or "pathId" attributes must be specified.
+exception.resourceNotSpecified=A resource name must be specified for the eclipse.refreshLocal task.
+warning.projectDoesNotExist=Warning: project {0} does not exist and cannot be refreshed.
diff --git a/third_party/patches/mars/org.eclipse.core.resources/build.properties b/third_party/patches/mars/org.eclipse.core.resources/build.properties
new file mode 100644
index 0000000000..f646e2c10a
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/build.properties
@@ -0,0 +1,22 @@
+###############################################################################
+# Copyright (c) 2000, 2012 IBM Corporation and others.
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Eclipse Public License v1.0
+# which accompanies this distribution, and is available at
+# http://www.eclipse.org/legal/epl-v10.html
+#
+# Contributors:
+# IBM Corporation - initial API and implementation
+###############################################################################
+source..=src/
+output..=target/classes/
+src.includes = about.html,\
+ schema/
+bin.includes = META-INF/,\
+ OSGI-INF/,\
+ about.html,\
+ schema/,\
+ plugin.xml,\
+ plugin.properties,\
+ .
+javacWarnings..=-unavoidableGenericProblems
diff --git a/third_party/patches/mars/org.eclipse.core.resources/natives/make.bat b/third_party/patches/mars/org.eclipse.core.resources/natives/make.bat
new file mode 100644
index 0000000000..43da22c8b7
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/natives/make.bat
@@ -0,0 +1,21 @@
+@rem ***************************************************************************
+@rem Copyright (c) 2007, 2014 IBM Corporation and others.
+@rem All rights reserved. This program and the accompanying materials
+@rem are made available under the terms of the Eclipse Public License v1.0
+@rem which accompanies this distribution, and is available at
+@rem http://www.eclipse.org/legal/epl-v10.html
+@rem
+@rem Contributors:
+@rem IBM Corporation - initial API and implementation
+@rem ***************************************************************************
+REM build JNI header file
+cd ..\bin
+d:\vm\sun141\bin\javah org.eclipse.core.internal.resources.refresh.win32.ref
+move org_eclipse_core_internal_resources_refresh_win32_ref.h ..\a\ref.h
+
+REM compile and link
+cd ..\a
+set win_include=k:\dev\products\msvc60\vc98\include
+set jdk_include="d:\vm\sun141\include"
+set dll_name=win32refresh.dll
+cl -I%win_include% -I%jdk_include% -I%jdk_include%\win32 -LD ref.c -Fe%dll_name%
diff --git a/third_party/patches/mars/org.eclipse.core.resources/natives/readme.txt b/third_party/patches/mars/org.eclipse.core.resources/natives/readme.txt
new file mode 100644
index 0000000000..e27ecd6c00
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/natives/readme.txt
@@ -0,0 +1,3 @@
+This folder contains native code for supporting auto-refresh callbacks on Windows.
+This source is in the base plugin because there are multiple Windows fragments that
+share the same source.
\ No newline at end of file
diff --git a/third_party/patches/mars/org.eclipse.core.resources/natives/ref.c b/third_party/patches/mars/org.eclipse.core.resources/natives/ref.c
new file mode 100644
index 0000000000..5c9907de1b
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/natives/ref.c
@@ -0,0 +1,297 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2007 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+#include
+#include "ref.h"
+
+/*
+ * Class: org_eclipse_core_internal_resources_refresh_win32_Win32Natives
+ * Method: FindFirstChangeNotificationW
+ * Signature: (Ljava/lang/String;ZI)J
+ */
+JNIEXPORT jlong JNICALL Java_org_eclipse_core_internal_resources_refresh_win32_Win32Natives_FindFirstChangeNotificationW
+(JNIEnv * env, jclass this, jstring lpPathName, jboolean bWatchSubtree, jint dwNotifyFilter) {
+ jlong result;
+ jsize numberOfChars;
+ jchar *path;
+ const jchar *temp;
+
+ // create a new byte array to hold the prefixed and null terminated path
+ numberOfChars= (*env)->GetStringLength(env, lpPathName);
+ path= malloc((numberOfChars + 5) * sizeof(jchar));
+ //path= malloc((numberOfChars + 4) * sizeof(jchar));
+
+ // get the path characters from the vm, copy them, and release them
+ temp= (*env)->GetStringChars(env, lpPathName, JNI_FALSE);
+ memcpy(path + 4, temp, numberOfChars * sizeof(jchar));
+ (*env)->ReleaseStringChars(env, lpPathName, temp);
+
+ // prefix the path to enable long filenames, and null terminate it
+ path[0] = L'\\';
+ path[1] = L'\\';
+ path[2] = L'?';
+ path[3] = L'\\';
+ path[(numberOfChars + 4)] = L'\0';
+
+ // make the request and free the memory
+ //printf("%S\n", path);
+ result = (jlong) FindFirstChangeNotificationW(path, bWatchSubtree, dwNotifyFilter);
+ free(path);
+
+ return result;
+}
+
+/*
+ * Class: org_eclipse_core_internal_resources_refresh_win32_Win32Natives
+ * Method: FindFirstChangeNotificationA
+ * Signature: ([BZI)J
+ */
+JNIEXPORT jlong JNICALL Java_org_eclipse_core_internal_resources_refresh_win32_Win32Natives_FindFirstChangeNotificationA
+(JNIEnv * env, jclass this, jbyteArray lpPathName, jboolean bWatchSubtree, jint dwNotifyFilter) {
+ jlong result;
+ jsize numberOfChars;
+ jbyte *path, *temp;
+
+ // create a new byte array to hold the null terminated path
+ numberOfChars = (*env)->GetArrayLength(env, lpPathName);
+ path = malloc((numberOfChars + 1) * sizeof(jbyte));
+
+ // get the path bytes from the vm, copy them, and release them
+ temp = (*env)->GetByteArrayElements(env, lpPathName, 0);
+ memcpy(path, temp, numberOfChars * sizeof(jbyte));
+ (*env)->ReleaseByteArrayElements(env, lpPathName, temp, 0);
+
+ // null terminate the path, make the request, and release the path memory
+ path[numberOfChars] = '\0';
+ result = (jlong) FindFirstChangeNotificationA(path, bWatchSubtree, dwNotifyFilter);
+ free(path);
+
+ return result;
+}
+
+/*
+ * Class: org_eclipse_core_internal_resources_refresh_win32_Win32Natives
+ * Method: FindCloseChangeNotification
+ * Signature: (J)Z
+ */
+JNIEXPORT jboolean JNICALL Java_org_eclipse_core_internal_resources_refresh_win32_Win32Natives_FindCloseChangeNotification
+(JNIEnv *env, jclass this, jlong hChangeHandle){
+ return (jboolean) FindCloseChangeNotification((HANDLE) hChangeHandle);
+}
+
+/*
+ * Class: org_eclipse_core_internal_resources_refresh_win32_Win32Natives
+ * Method: FindNextChangeNotification
+ * Signature: (J)Z
+ */
+JNIEXPORT jboolean JNICALL Java_org_eclipse_core_internal_resources_refresh_win32_Win32Natives_FindNextChangeNotification
+(JNIEnv *env, jclass this, jlong hChangeHandle){
+ return (jboolean) FindNextChangeNotification((HANDLE) hChangeHandle);
+}
+
+/*
+ * Class: org_eclipse_core_internal_resources_refresh_win32_Win32Natives
+ * Method: WaitForMultipleObjects
+ * Signature: (I[JZI)I
+ */
+JNIEXPORT jint JNICALL Java_org_eclipse_core_internal_resources_refresh_win32_Win32Natives_WaitForMultipleObjects
+(JNIEnv *env, jclass this, jint nCount, jlongArray lpHandles, jboolean bWaitAll, jint dwMilliseconds) {
+ int i;
+ jint result;
+ HANDLE handles[MAXIMUM_WAIT_OBJECTS];
+ jlong *handlePointers = (*env)->GetLongArrayElements(env, lpHandles, 0);
+
+ for (i = 0; i < nCount; i++) {
+ handles[i] = (HANDLE) handlePointers[i];
+ }
+
+ result = WaitForMultipleObjects(nCount, handles, bWaitAll, dwMilliseconds);
+ (*env)->ReleaseLongArrayElements(env, lpHandles, handlePointers, 0);
+
+ return result;
+}
+
+/*
+ * Class: org_eclipse_core_internal_resources_refresh_win32_Win32Natives
+ * Method: IsUnicode
+ * Signature: ()Z
+ */
+JNIEXPORT jboolean JNICALL Java_org_eclipse_core_internal_resources_refresh_win32_Win32Natives_IsUnicode
+ (JNIEnv *env, jclass this) {
+ OSVERSIONINFO osvi;
+ memset(&osvi, 0, sizeof(OSVERSIONINFO));
+ osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
+ if (! GetVersionEx (&osvi) )
+ return JNI_FALSE;
+ if (osvi.dwMajorVersion >= 5)
+ return JNI_TRUE;
+ return JNI_FALSE;
+}
+
+/*
+ * Class: org_eclipse_core_internal_resources_refresh_win32_Win32Natives
+ * Method: GetLastError
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_eclipse_core_internal_resources_refresh_win32_Win32Natives_GetLastError
+(JNIEnv *env, jclass this){
+ return GetLastError();
+}
+
+/*
+ * Class: org_eclipse_core_internal_resources_refresh_win32_Win32Natives
+ * Method: FILE_NOTIFY_CHANGE_LAST_WRITE
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_eclipse_core_internal_resources_refresh_win32_Win32Natives_FILE_1NOTIFY_1CHANGE_1LAST_1WRITE
+(JNIEnv *env, jclass this) {
+ return FILE_NOTIFY_CHANGE_LAST_WRITE;
+}
+
+/*
+ * Class: org_eclipse_core_internal_resources_refresh_win32_Win32Natives
+ * Method: FILE_NOTIFY_CHANGE_DIR_NAME
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_eclipse_core_internal_resources_refresh_win32_Win32Natives_FILE_1NOTIFY_1CHANGE_1DIR_1NAME
+(JNIEnv *env, jclass this) {
+ return FILE_NOTIFY_CHANGE_DIR_NAME;
+}
+
+/*
+ * Class: org_eclipse_core_internal_resources_refresh_win32_Win32Natives
+ * Method: FILE_NOTIFY_CHANGE_ATTRIBUTES
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_eclipse_core_internal_resources_refresh_win32_Win32Natives_FILE_1NOTIFY_1CHANGE_1ATTRIBUTES
+(JNIEnv *env, jclass this) {
+ return FILE_NOTIFY_CHANGE_ATTRIBUTES;
+}
+
+/*
+ * Class: org_eclipse_core_internal_resources_refresh_win32_Win32Natives
+ * Method: FILE_NOTIFY_CHANGE_SIZE
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_eclipse_core_internal_resources_refresh_win32_Win32Natives_FILE_1NOTIFY_1CHANGE_1SIZE
+(JNIEnv *env, jclass this) {
+ return FILE_NOTIFY_CHANGE_SIZE;
+}
+
+/*
+ * Class: org_eclipse_core_internal_resources_refresh_win32_Win32Natives
+ * Method: FILE_NOTIFY_CHANGE_FILE_NAME
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_eclipse_core_internal_resources_refresh_win32_Win32Natives_FILE_1NOTIFY_1CHANGE_1FILE_1NAME
+(JNIEnv *env, jclass this) {
+ return FILE_NOTIFY_CHANGE_FILE_NAME;
+}
+
+
+/*
+ * Class: org_eclipse_core_internal_resources_refresh_win32_Win32Natives
+ * Method: FILE_NOTIFY_CHANGE_SECURITY
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_eclipse_core_internal_resources_refresh_win32_Win32Natives_FILE_1NOTIFY_1CHANGE_1SECURITY
+(JNIEnv *env, jclass this) {
+ return FILE_NOTIFY_CHANGE_SECURITY;
+}
+
+/*
+ * Class: org_eclipse_core_internal_resources_refresh_win32_Win32Natives
+ * Method: MAXIMUM_WAIT_OBJECTS
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_eclipse_core_internal_resources_refresh_win32_Win32Natives_MAXIMUM_1WAIT_1OBJECTS
+(JNIEnv *env, jclass this) {
+ return MAXIMUM_WAIT_OBJECTS;
+}
+
+/*
+ * Class: org_eclipse_core_internal_resources_refresh_win32_Win32Natives
+ * Method: MAX_PATH
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_eclipse_core_internal_resources_refresh_win32_Win32Natives_MAX_1PATH
+(JNIEnv *env, jclass this) {
+ return MAX_PATH;
+}
+
+/*
+ * Class: org_eclipse_core_internal_resources_refresh_win32_Win32Natives
+ * Method: INFINITE
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_eclipse_core_internal_resources_refresh_win32_Win32Natives_INFINITE
+(JNIEnv *env, jclass this) {
+ return INFINITE;
+}
+
+/*
+ * Class: org_eclipse_core_internal_resources_refresh_win32_Win32Natives
+ * Method: WAIT_OBJECT_0
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_eclipse_core_internal_resources_refresh_win32_Win32Natives_WAIT_1OBJECT_10
+(JNIEnv *env, jclass this) {
+ return WAIT_OBJECT_0;
+}
+
+/*
+ * Class: org_eclipse_core_internal_resources_refresh_win32_Win32Natives
+ * Method: WAIT_FAILED
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_eclipse_core_internal_resources_refresh_win32_Win32Natives_WAIT_1FAILED
+(JNIEnv *env, jclass this) {
+ return WAIT_FAILED;
+}
+
+/*
+ * Class: org_eclipse_core_internal_resources_refresh_win32_Win32Natives
+ * Method: WAIT_TIMEOUT
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_eclipse_core_internal_resources_refresh_win32_Win32Natives_WAIT_1TIMEOUT
+(JNIEnv *env, jclass this) {
+ return WAIT_TIMEOUT;
+}
+
+/*
+ * Class: org_eclipse_core_internal_resources_refresh_win32_Win32Natives
+ * Method: ERROR_INVALID_HANDLE
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_eclipse_core_internal_resources_refresh_win32_Win32Natives_ERROR_1INVALID_1HANDLE
+(JNIEnv *env, jclass this) {
+ return ERROR_INVALID_HANDLE;
+}
+
+/*
+ * Class: org_eclipse_core_internal_resources_refresh_win32_Win32Natives
+ * Method: ERROR_SUCCESS
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_eclipse_core_internal_resources_refresh_win32_Win32Natives_ERROR_1SUCCESS
+(JNIEnv *env, jclass this) {
+ return ERROR_SUCCESS;
+}
+
+/*
+ * Class: org_eclipse_core_internal_resources_refresh_win32_Win32Natives
+ * Method: INVALID_HANDLE_VALUE
+ * Signature: ()J
+ */
+JNIEXPORT jlong JNICALL Java_org_eclipse_core_internal_resources_refresh_win32_Win32Natives_INVALID_1HANDLE_1VALUE
+(JNIEnv * env, jclass this) {
+ return (jlong)INVALID_HANDLE_VALUE;
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/natives/ref.h b/third_party/patches/mars/org.eclipse.core.resources/natives/ref.h
new file mode 100644
index 0000000000..15c5ba9ffb
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/natives/ref.h
@@ -0,0 +1,213 @@
+/*
+ * Copyright (c) 2004, 2007 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include
+/* Header for class org_eclipse_core_internal_resources_refresh_win32_Win32Natives */
+
+#ifndef _Included_org_eclipse_core_internal_resources_refresh_win32_Win32Natives
+#define _Included_org_eclipse_core_internal_resources_refresh_win32_Win32Natives
+#ifdef __cplusplus
+extern "C" {
+#endif
+/* Inaccessible static: INVALID_HANDLE_VALUE */
+/* Inaccessible static: ERROR_SUCCESS */
+/* Inaccessible static: ERROR_INVALID_HANDLE */
+/* Inaccessible static: FILE_NOTIFY_ALL */
+/* Inaccessible static: MAXIMUM_WAIT_OBJECTS */
+/* Inaccessible static: MAX_PATH */
+/* Inaccessible static: INFINITE */
+/* Inaccessible static: WAIT_TIMEOUT */
+/* Inaccessible static: WAIT_OBJECT_0 */
+/* Inaccessible static: WAIT_FAILED */
+/* Inaccessible static: FILE_NOTIFY_CHANGE_FILE_NAME */
+/* Inaccessible static: FILE_NOTIFY_CHANGE_DIR_NAME */
+/* Inaccessible static: FILE_NOTIFY_CHANGE_ATTRIBUTES */
+/* Inaccessible static: FILE_NOTIFY_CHANGE_SIZE */
+/* Inaccessible static: FILE_NOTIFY_CHANGE_LAST_WRITE */
+/* Inaccessible static: FILE_NOTIFY_CHANGE_SECURITY */
+/* Inaccessible static: UNICODE */
+/*
+ * Class: org_eclipse_core_internal_resources_refresh_win32_Win32Natives
+ * Method: FindFirstChangeNotificationW
+ * Signature: (Ljava/lang/String;ZI)J
+ */
+JNIEXPORT jlong JNICALL Java_org_eclipse_core_internal_resources_refresh_win32_Win32Natives_FindFirstChangeNotificationW
+ (JNIEnv *, jclass, jstring, jboolean, jint);
+
+/*
+ * Class: org_eclipse_core_internal_resources_refresh_win32_Win32Natives
+ * Method: FindFirstChangeNotificationA
+ * Signature: ([BZI)J
+ */
+JNIEXPORT jlong JNICALL Java_org_eclipse_core_internal_resources_refresh_win32_Win32Natives_FindFirstChangeNotificationA
+ (JNIEnv *, jclass, jbyteArray, jboolean, jint);
+
+/*
+ * Class: org_eclipse_core_internal_resources_refresh_win32_Win32Natives
+ * Method: FindCloseChangeNotification
+ * Signature: (J)Z
+ */
+JNIEXPORT jboolean JNICALL Java_org_eclipse_core_internal_resources_refresh_win32_Win32Natives_FindCloseChangeNotification
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_eclipse_core_internal_resources_refresh_win32_Win32Natives
+ * Method: FindNextChangeNotification
+ * Signature: (J)Z
+ */
+JNIEXPORT jboolean JNICALL Java_org_eclipse_core_internal_resources_refresh_win32_Win32Natives_FindNextChangeNotification
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_eclipse_core_internal_resources_refresh_win32_Win32Natives
+ * Method: WaitForMultipleObjects
+ * Signature: (I[JZI)I
+ */
+JNIEXPORT jint JNICALL Java_org_eclipse_core_internal_resources_refresh_win32_Win32Natives_WaitForMultipleObjects
+ (JNIEnv *, jclass, jint, jlongArray, jboolean, jint);
+
+/*
+ * Class: org_eclipse_core_internal_resources_refresh_win32_Win32Natives
+ * Method: IsUnicode
+ * Signature: ()Z
+ */
+JNIEXPORT jboolean JNICALL Java_org_eclipse_core_internal_resources_refresh_win32_Win32Natives_IsUnicode
+ (JNIEnv *, jclass);
+
+/*
+ * Class: org_eclipse_core_internal_resources_refresh_win32_Win32Natives
+ * Method: GetLastError
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_eclipse_core_internal_resources_refresh_win32_Win32Natives_GetLastError
+ (JNIEnv *, jclass);
+
+/*
+ * Class: org_eclipse_core_internal_resources_refresh_win32_Win32Natives
+ * Method: FILE_NOTIFY_CHANGE_LAST_WRITE
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_eclipse_core_internal_resources_refresh_win32_Win32Natives_FILE_1NOTIFY_1CHANGE_1LAST_1WRITE
+ (JNIEnv *, jclass);
+
+/*
+ * Class: org_eclipse_core_internal_resources_refresh_win32_Win32Natives
+ * Method: FILE_NOTIFY_CHANGE_DIR_NAME
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_eclipse_core_internal_resources_refresh_win32_Win32Natives_FILE_1NOTIFY_1CHANGE_1DIR_1NAME
+ (JNIEnv *, jclass);
+
+/*
+ * Class: org_eclipse_core_internal_resources_refresh_win32_Win32Natives
+ * Method: FILE_NOTIFY_CHANGE_ATTRIBUTES
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_eclipse_core_internal_resources_refresh_win32_Win32Natives_FILE_1NOTIFY_1CHANGE_1ATTRIBUTES
+ (JNIEnv *, jclass);
+
+/*
+ * Class: org_eclipse_core_internal_resources_refresh_win32_Win32Natives
+ * Method: FILE_NOTIFY_CHANGE_SIZE
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_eclipse_core_internal_resources_refresh_win32_Win32Natives_FILE_1NOTIFY_1CHANGE_1SIZE
+ (JNIEnv *, jclass);
+
+/*
+ * Class: org_eclipse_core_internal_resources_refresh_win32_Win32Natives
+ * Method: FILE_NOTIFY_CHANGE_FILE_NAME
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_eclipse_core_internal_resources_refresh_win32_Win32Natives_FILE_1NOTIFY_1CHANGE_1FILE_1NAME
+ (JNIEnv *, jclass);
+
+/*
+ * Class: org_eclipse_core_internal_resources_refresh_win32_Win32Natives
+ * Method: FILE_NOTIFY_CHANGE_SECURITY
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_eclipse_core_internal_resources_refresh_win32_Win32Natives_FILE_1NOTIFY_1CHANGE_1SECURITY
+ (JNIEnv *, jclass);
+
+/*
+ * Class: org_eclipse_core_internal_resources_refresh_win32_Win32Natives
+ * Method: MAXIMUM_WAIT_OBJECTS
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_eclipse_core_internal_resources_refresh_win32_Win32Natives_MAXIMUM_1WAIT_1OBJECTS
+ (JNIEnv *, jclass);
+
+/*
+ * Class: org_eclipse_core_internal_resources_refresh_win32_Win32Natives
+ * Method: MAX_PATH
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_eclipse_core_internal_resources_refresh_win32_Win32Natives_MAX_1PATH
+ (JNIEnv *, jclass);
+
+/*
+ * Class: org_eclipse_core_internal_resources_refresh_win32_Win32Natives
+ * Method: INFINITE
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_eclipse_core_internal_resources_refresh_win32_Win32Natives_INFINITE
+ (JNIEnv *, jclass);
+
+/*
+ * Class: org_eclipse_core_internal_resources_refresh_win32_Win32Natives
+ * Method: WAIT_OBJECT_0
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_eclipse_core_internal_resources_refresh_win32_Win32Natives_WAIT_1OBJECT_10
+ (JNIEnv *, jclass);
+
+/*
+ * Class: org_eclipse_core_internal_resources_refresh_win32_Win32Natives
+ * Method: WAIT_FAILED
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_eclipse_core_internal_resources_refresh_win32_Win32Natives_WAIT_1FAILED
+ (JNIEnv *, jclass);
+
+/*
+ * Class: org_eclipse_core_internal_resources_refresh_win32_Win32Natives
+ * Method: WAIT_TIMEOUT
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_eclipse_core_internal_resources_refresh_win32_Win32Natives_WAIT_1TIMEOUT
+ (JNIEnv *, jclass);
+
+/*
+ * Class: org_eclipse_core_internal_resources_refresh_win32_Win32Natives
+ * Method: ERROR_INVALID_HANDLE
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_eclipse_core_internal_resources_refresh_win32_Win32Natives_ERROR_1INVALID_1HANDLE
+ (JNIEnv *, jclass);
+
+/*
+ * Class: org_eclipse_core_internal_resources_refresh_win32_Win32Natives
+ * Method: ERROR_SUCCESS
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_eclipse_core_internal_resources_refresh_win32_Win32Natives_ERROR_1SUCCESS
+ (JNIEnv *, jclass);
+
+/*
+ * Class: org_eclipse_core_internal_resources_refresh_win32_Win32Natives
+ * Method: INVALID_HANDLE_VALUE
+ * Signature: ()J
+ */
+JNIEXPORT jlong JNICALL Java_org_eclipse_core_internal_resources_refresh_win32_Win32Natives_INVALID_1HANDLE_1VALUE
+ (JNIEnv *, jclass);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/third_party/patches/mars/org.eclipse.core.resources/plugin.properties b/third_party/patches/mars/org.eclipse.core.resources/plugin.properties
new file mode 100644
index 0000000000..1c45ea381d
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/plugin.properties
@@ -0,0 +1,40 @@
+###############################################################################
+# Copyright (c) 2000, 2014 IBM Corporation and others.
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Eclipse Public License v1.0
+# which accompanies this distribution, and is available at
+# http://www.eclipse.org/legal/epl-v10.html
+#
+# Contributors:
+# IBM Corporation - initial API and implementation
+###############################################################################
+pluginName = Core Resource Management
+providerName = Eclipse.org
+buildersName = Builders
+markersName = Markers
+naturesName = Project Natures
+validatorName = File Modification Validator
+hookName = Move/Delete Hook
+teamHookName = Team Hook
+preferencesContentTypeName = Preferences
+refreshProvidersName=Refresh Providers
+modelProviders=Model Providers
+filterMatchers=Filter Matchers
+preferencesExtPtName=Resource Preferences
+resourceModelName=File System Resources
+variableProviders=Variable Providers
+
+markerName = Marker
+problemName = Problem
+taskName = Task
+bookmarkName = Bookmark
+textName = Text
+
+win32FragmentName = Core Resource Management Win32 Fragment
+compatibilityFragmentName = Core Resource Management Compatibility Fragment
+win32MonitorFactoryName = Windows Auto-refresh monitor
+
+regexFilterProvider.description = Matches file and folder names with a regular expression
+regexFilterProvider.name = Regular Expression
+
+trace.component.label = Platform Core Resources
diff --git a/third_party/patches/mars/org.eclipse.core.resources/plugin.xml b/third_party/patches/mars/org.eclipse.core.resources/plugin.xml
new file mode 100644
index 0000000000..7f8663ce31
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/plugin.xml
@@ -0,0 +1,260 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/third_party/patches/mars/org.eclipse.core.resources/schema/builders.exsd b/third_party/patches/mars/org.eclipse.core.resources/schema/builders.exsd
new file mode 100644
index 0000000000..8db174734f
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/schema/builders.exsd
@@ -0,0 +1,216 @@
+
+
+
+
+
+
+
+
+ The workspace supports the notion of an incremental
+project builder (or "builder" for short"). The job
+of a builder is to process a set of resource changes
+(supplied as a resource delta). For example, a Java
+builder would recompile changed Java files and produce
+new class files.
+<p>
+Builders are configured on a per-project basis and run
+automatically when resources within their project are
+changed. As such, builders should be fast and scale
+with respect to the amount of change rather than the
+number of resources in the project. This typically
+implies that builders are able to incrementally update
+their "built state".
+<p>
+The builders extension-point allows builder writers
+to register their builder implementation under a
+symbolic name that is then used from within the
+workspace to find and run builders. The symbolic
+name is the id of the builder extension. When defining a builder extension, users are encouraged to include a human-readable value for the "name" attribute which identifies their builder and potentially may be presented to users.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ a fully qualified identifier of the target extension point
+
+
+
+
+
+
+ an optional identifier of the extension instance
+
+
+
+
+
+
+ an optional name of the extension instance
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ "<tt>true</tt>" or "<tt>false</tt>" indicating whether the builder is owned by
+a project nature. If "<tt>true</tt>" and no corresponding nature is
+found, this builder will not run but will remain in the project's
+build spec. If the attribute is not specified, it is assumed to be "<tt>false</tt>".
+
+
+
+
+
+
+ "<tt>true</tt>" or "<tt>false</tt>" indicating whether the builder allows customization of what build triggers it will respond to. If "<tt>true</tt>", clients will be able to use the API <tt>ICommand.setBuilding</tt> to specify if this builder should be run for a particular build trigger. If the attribute is not specified, it is assumed to be "<tt>false</tt>".
+
+
+
+
+
+
+ "<tt>true</tt>" or "<tt>false</tt>" indicating whether the builder should be called on <tt>INCREMENTAL_BUILD</tt> when the resource deltas for its affected projects are empty. If "<tt>true</tt>", the builder will always be called on builds of type <tt>INCREMENTAL_BUILD</tt>, regardless of whether any resources in the affected projects have changed. If "<tt>false</tt>" or unspecified, the builder will only be called when affected projects have changed. The value of this attribute does not affect the behaviour of builders for other build triggers, such as <tt>AUTO_BUILD</tt> or <tt>FULL_BUILD</tt>This attribute is intended to be used by builders that incrementally react to changing circumstances outside of the workspace, such as external libraries.
+
+
+
+
+
+
+ "<tt>true</tt>" or "<tt>false</tt>" indicating whether the builder supports multiple build configurations. If "<tt>true</tt>" the builder is provided with a configuration specific delta.
+If "<tt>false</tt>" the delta is the delta since build was last called.
+<p>
+ If the attribute is not specified, it is assumed to be "<tt>false</tt>".
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ the fully-qualified name of a subclass of
+<samp>org.eclipse.core.resources.IncrementalProjectBuilder</samp>.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ the name of this parameter made available to
+instances of the specified builder class
+
+
+
+
+
+
+ an arbitrary value associated with the given
+name and made available to instances of the
+specified builder class
+
+
+
+
+
+
+
+
+
+
+
+ Following is an example of a builder configuration:
+
+<p>
+<pre>
+ <extension id="coolbuilder" name="Cool Builder" point="org.eclipse.core.resources.builders">
+ <builder hasNature="false">
+ <run class="com.xyz.builders.Cool">
+ <parameter name="optimize" value="true"/>
+ <parameter name="comment" value="Produced by the Cool Builder"/>
+ </run>
+ </builder>
+ </extension>
+</pre>
+</p>
+
+If this extension was defined in a plug-in with id "com.xyz.coolplugin", the fully qualified name of this builder would be "com.xyz.coolplugin.coolbuilder".
+
+
+
+
+
+
+
+
+ The value of the class attribute must represent a
+subclass of <samp>org.eclipse.core.resources.IncrementalProjectBuilder</samp>.
+
+
+
+
+
+
+
+
+
+ The platform itself does not have any predefined
+builders. Particular product installs may include
+builders as required.
+
+
+
+
+
+
+
+
+ Copyright (c) 2002, 2010 IBM Corporation and others.<br>
+All rights reserved. This program and the accompanying materials are made
+available under the terms of the Eclipse Public License v1.0 which
+accompanies this distribution, and is available at
+<a href="http://www.eclipse.org/legal/epl-v10.html">http://www.eclipse.org/legal/epl-v10.html</a>
+
+
+
+
diff --git a/third_party/patches/mars/org.eclipse.core.resources/schema/fileModificationValidator.exsd b/third_party/patches/mars/org.eclipse.core.resources/schema/fileModificationValidator.exsd
new file mode 100644
index 0000000000..2336209991
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/schema/fileModificationValidator.exsd
@@ -0,0 +1,111 @@
+
+
+
+
+
+
+
+
+ For providing an implementation of an IFileModificationValidator to be used in the validate-edit
+and validate-save mechanism. This extension point tolerates at most one extension.
+
+
+
+
+
+
+
+
+
+
+
+ a fully qualified identifier of the target extension point
+
+
+
+
+
+
+ an optional identifier of the extension instance
+
+
+
+
+
+
+ an optional name of the extension instance
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ a fully qualified name of a class which implements <samp>org.eclipse.core.resources.IFileModificationValidator</samp>.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The following is an example of using the <tt>fileModificationValidator</tt> extension point:
+<p>
+<pre>
+ <extension point="org.eclipse.core.resources.fileModificationValidator">
+ <fileModificationValidator class="org.eclipse.vcm.internal.VCMFileModificationValidator"/>
+ </extension>
+</pre>
+</p>
+
+
+
+
+
+
+
+
+ The value of the <samp>class</samp> attribute must represent an implementation of
+<samp>org.eclipse.core.resources.IFileModificationValidator</samp>.
+
+
+
+
+
+
+
+
+ The Team component will generally provide the implementation of the file modification validator. The extension point should be used by any other clients.
+
+
+
+
+
+
+
+
+ Copyright (c) 2002, 2005 IBM Corporation and others.<br>
+All rights reserved. This program and the accompanying materials are made
+available under the terms of the Eclipse Public License v1.0 which
+accompanies
+this distribution, and is available at
+<a
+href="http://www.eclipse.org/legal/epl-v10.html">http://www.eclipse.org/legal/epl-v10.html</a>
+
+
+
+
diff --git a/third_party/patches/mars/org.eclipse.core.resources/schema/filterMatchers.exsd b/third_party/patches/mars/org.eclipse.core.resources/schema/filterMatchers.exsd
new file mode 100644
index 0000000000..5bcd92a610
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/schema/filterMatchers.exsd
@@ -0,0 +1,187 @@
+
+
+
+
+
+
+
+
+ The filter matchers extension point allows plug-ins to contribute matchers. The matchers are used by filters applied on containers (folders or projects) to include or exclude some file system objects while populating the resources tree.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ A hint specifying that this filter should be called first or last among the list of active filters for a given container. Often specified for performance reason.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 3.6
+
+
+
+
+
+
+
+
+ The following is an example of using the modelProvider extension point.
+(in file plugin.xml)
+<pre>
+ <extension
+ point="org.eclipse.core.resources.filterMatchers">
+ <filterMatcher
+ argumentType="string"
+ class="org.eclipse.core.internal.resources.RegexFileInfoMatcher">
+ </filterMatcher>
+ </extension>
+</pre>
+
+
+
+
+
+
+
+
+ The value of the class attribute must represent a
+subclass of <samp>org.eclipse.core.resources.filtermatchers.AbstractFileInfoMatcher</samp>.
+
+
+
+
+
+
+
+
+ The core resource plugin provides the regex matcher, allowing the user to specify string arguments matching the specification supported by java.util.regex.Pattern.
+
+
+
+
+
+
+
+
+ Copyright (c) 2008, 2009 Freescale Semiconductor and others.<br>
+All rights reserved. This program and the accompanying materials are made
+available under the terms of the Eclipse Public License v1.0 which
+accompanies
+this distribution, and is available at
+<a href="http://www.eclipse.org/legal/epl-v10.html">http://www.eclipse.org/legal/epl-v10.html</a>
+
+
+
+
diff --git a/third_party/patches/mars/org.eclipse.core.resources/schema/markers.exsd b/third_party/patches/mars/org.eclipse.core.resources/schema/markers.exsd
new file mode 100644
index 0000000000..a263442c10
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/schema/markers.exsd
@@ -0,0 +1,167 @@
+
+
+
+
+
+
+
+
+ The workspace supports the notion of markers on arbitrary
+resources. A marker is a kind of metadata
+(similar to properties) which can be used to
+tag resources with user information. Markers are
+optionally persisted by the workspace whenever a
+workspace save or snapshot is done.
+<p>
+Users can define and query for markers of a given type.
+Marker types are defined in a hierarchy that supports
+multiple-inheritance. Marker type definitions also
+specify a number attributes which must or may be
+present on a marker of that type as well as whether
+or not markers of that type should be persisted.
+<p>
+The markers extension-point allows marker writers to
+register their marker types under a symbolic name that
+is then used from within the workspace to create and
+query markers. The symbolic name is the id of the
+marker extension. When defining a marker extension,
+users are encouraged to include a human-readable value
+for the "name" attribute which indentifies their marker
+and potentially may be presented to users.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ a fully qualified identifier of the target extension point
+
+
+
+
+
+
+ a required identifier of the extension instance
+
+
+
+
+
+
+ an optional name of the extension instance
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ the fully-qualified id of a marker super type (i.e., a marker type defined by another marker extension)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ "<tt>true</tt>" or "<tt>false</tt>" indicating whether or not markers of this type
+should be persisted by the workspace. If the persistent characteristic
+is not specified, the marker type is <b>not</b> persisted.
+
+
+
+
+
+
+
+
+
+
+
+ the name of an attribute which may be present on markers of this type
+
+
+
+
+
+
+
+
+
+
+
+ Following is an example of a marker configuration:
+
+<p>
+<pre>
+ <extension id="com.xyz.coolMarker" point="org.eclipse.core.resources.markers" name="Cool Marker">
+ <persistent value="true"/>
+ <super type="org.eclipse.core.resources.problemmarker"/>
+ <super type="org.eclipse.core.resources.textmarker"/>
+ <attribute name="owner"/>
+ </extension>
+</pre>
+</p>
+
+
+
+
+
+
+
+
+ All markers, regardless of their type, are instances of
+<samp>org.eclipse.core.resources.IMarker</samp>.
+
+
+
+
+
+
+
+
+
+
+ The platform itself has a number of pre-defined
+marker types. Particular product installs may
+include additional markers as required.
+
+
+
+
+
+
+
+
+ Copyright (c) 2002, 2008 IBM Corporation and others.<br>
+All rights reserved. This program and the accompanying materials are made
+available under the terms of the Eclipse Public License v1.0 which
+accompanies
+this distribution, and is available at
+<a
+href="http://www.eclipse.org/legal/epl-v10.html">http://www.eclipse.org/legal/epl-v10.html</a>
+
+
+
+
diff --git a/third_party/patches/mars/org.eclipse.core.resources/schema/modelProviders.exsd b/third_party/patches/mars/org.eclipse.core.resources/schema/modelProviders.exsd
new file mode 100644
index 0000000000..aa9b999c97
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/schema/modelProviders.exsd
@@ -0,0 +1,159 @@
+
+
+
+
+
+
+
+
+ A model provider serves two purposes. Firstly, it is a means of grouping the resource mappings of a single model together for the purposes of performing operations, display, etc. Also, it provides a means of mapping a set of file system resources to the resource mappings that describe how a model maps to resources.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The value of the class attribute must represent an implementation of <tt>org.eclipse.core.resources.mapping.ModelProvider</tt>.
+
+
+
+
+
+
+
+
+ 3.2
+
+
+
+
+
+
+
+
+ The following is an example of using the modelProvider extension point.
+(in file plugin.xml)
+<pre>
+ <extension
+ id="modelProvider"
+ name="Library Model Provider"
+ point="org.eclipse.core.resources.modelProviders">
+ <modelProvider
+ class="org.eclipse.examples.library.LibraryModelProvider"
+ name="Library Model Provider"/>
+ <extends-model id="org.eclipse.core.resources.modelProvider"/>
+ <enablement>
+ <with variable="affectedNatures">
+ <iterate operator="or">
+ <equals value="org.eclipse.team.examples.library.nature"/>
+ </iterate>
+ </with>
+ <with variable="element">
+ <instanceof value="org.eclipse.core.resources.IFile"/>
+ </with>
+ </enablement>
+ </extension>
+</pre>
+
+
+
+
+
+
+
+
+ The platform itself does not have any predefined
+model providers. Particular product installs may include
+model providers as required.
+
+
+
+
+
+
+
+
+ Copyright (c) 2005, 2010 IBM Corporation and others.<br>
+All rights reserved. This program and the accompanying materials are made
+available under the terms of the Eclipse Public License v1.0 which
+accompanies
+this distribution, and is available at
+<a
+href="http://www.eclipse.org/legal/epl-v10.html">http://www.eclipse.org/legal/epl-v10.html</a>
+
+
+
+
diff --git a/third_party/patches/mars/org.eclipse.core.resources/schema/moveDeleteHook.exsd b/third_party/patches/mars/org.eclipse.core.resources/schema/moveDeleteHook.exsd
new file mode 100644
index 0000000000..46cf633f16
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/schema/moveDeleteHook.exsd
@@ -0,0 +1,120 @@
+
+
+
+
+
+
+
+
+ For providing an implementation of an IMoveDeleteHook to be used in the IResource.move and IResource.delete mechanism. This extension point tolerates at most one extension.
+
+
+
+
+
+
+
+
+
+
+
+ a fully qualified identifier of the target extension point
+
+
+
+
+
+
+ an optional identifier of the extension instance
+
+
+
+
+
+
+ an optional name of the extension instance
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ the fully-qualified name of a class which implements
+<samp>org.eclipse.core.resources.team.IMoveDeleteHook</samp>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 2.0
+
+
+
+
+
+
+
+
+ The following is an example of using the moveDeleteHook extension point.
+(in file plugin.xml)
+<pre>
+ <extension point="org.eclipse.core.resources.moveDeleteHook">
+ <moveDeleteHook class="org.eclipse.team.internal.MoveDeleteHook"/>
+ </extension>
+</pre>
+
+
+
+
+
+
+
+
+ The value of the class attribute must represent an implementation of <tt>org.eclipse.core.resources.team.IMoveDeleteHook</tt>.
+
+
+
+
+
+
+
+
+ The Team component will generally provide the implementation of the move/delete hook. The extension point should not be used by any other clients.
+
+
+
+
+
+
+
+
+ Copyright (c) 2002, 2005 IBM Corporation and others.<br>
+All rights reserved. This program and the accompanying materials are made
+available under the terms of the Eclipse Public License v1.0 which
+accompanies
+this distribution, and is available at
+<a
+href="http://www.eclipse.org/legal/epl-v10.html">http://www.eclipse.org/legal/epl-v10.html</a>
+
+
+
+
diff --git a/third_party/patches/mars/org.eclipse.core.resources/schema/natures.exsd b/third_party/patches/mars/org.eclipse.core.resources/schema/natures.exsd
new file mode 100644
index 0000000000..42cf2493d8
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/schema/natures.exsd
@@ -0,0 +1,334 @@
+
+
+
+
+
+
+
+
+ The workspace supports the notion of project natures
+(or "natures" for short"). A nature associates lifecycle
+behaviour with a project. Natures are installed on
+a per-project basis using the setDescription method defined on
+<samp>org.eclipse.core.resources.IProject</samp>. They are
+configured when added to a project and deconfigured
+when removed from the project. For example, the Java nature
+might install a Java builder and do other project
+configuration when added to a project
+<p>
+The natures extension-point allows nature writers
+to register their nature implementation under a
+symbolic name that is then used from within the
+workspace to find and configure natures.
+The symbolic name is id of the nature extension. When
+defining a nature extension, users are encouraged to include a
+human-readable value for the "name" attribute which identifies
+their meaning and potentially may be presented to users.
+</p>
+<p>
+Natures can specify relationship constraints with other natures.
+The "one-of-nature" constraint specifies that at most one nature
+belong to a given set can exist on a project at any given time.
+This enforces mutual exclusion between natures that are not compatible
+with each other. The "requires-nature" constraint specifies a
+dependency on another nature. When a nature is added to a project,
+all required natures must also be added. The natures are guaranteed
+to be configured and deconfigured in such a way that their required
+natures will always be configured before them and deconfigured after
+them. For this reason, cyclic dependencies between natures are not
+permitted.
+</p>
+<p>
+Natures cannot be added to or removed from a project if that change would
+violate any constraints that were previously satisfied. If a nature is
+configured on a project, but later finds that its constraints are not
+satisfied, that nature and all natures that require it are marked as
+<i>disabled</i>, but remain on the project. This can happen, for example,
+when a required nature goes missing from the install. Natures that are
+missing from the install, and natures involved in dependency cycles are
+also marked as disabled.
+</p>
+<p>
+Natures can also specify which incremental project builders, if any, are
+configured by them. With this information, the workspace will ensure that
+builders will only run when their corresponding nature is present and
+enabled on the project being built. If a nature is removed from a project,
+but the nature's deconfigure method fails to remove its corresponding builders,
+the workspace will remove those builders from the spec automatically. It
+is not permitted for two natures to specify the same incremental project builder
+in their markup.
+</p>
+<p>
+Natures also have the ability to disallow the creation of linked resources on projects they are associated with. By setting the <code>allowLinking</code> attribute to "false", a nature can declare that linked resources should never be created. This feature is new in release 2.1.
+</p>
+<p>
+Starting with release 3.1, natures can declare affinity
+with arbitrary content types, affecting the way content
+type determination happens for files in the workspace.
+In case of conflicts (two or more content types deemed
+equally suitable for a given file), the content type having affinity with any of the natures configured for the corresponding project will be chosen.
+</p>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ a fully qualified identifier of the target extension point
+
+
+
+
+
+
+ The simple identifier of the nature. This is appended to the plug-in id to form the fully qualified nature id.
+
+
+
+
+
+
+ an optional name of the extension instance
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ the fully-qualified name of a class which implements
+<samp>org.eclipse.core.resources.IProjectNature</samp>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ the name of this parameter made available to instances of the specified nature class
+
+
+
+
+
+
+ an arbitrary value associated with the given name and made available to instances of the specificed nature class
+
+
+
+
+
+
+
+
+
+
+
+ the name of an exclusive project nature category.
+
+
+
+
+
+
+
+
+
+
+
+ the fully-qualified id of another nature extension that this nature extension requires.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ the fully-qualified id of an incremental project builder extension that this nature controls.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ an option to specify whether this nature allows the creation of linked resources. By default, linking is allowed.
+
+
+
+
+
+
+
+
+
+
+
+ the fully-qualified id of a content type associated to this nature.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Following is an example of three nature configurations. The
+waterNature and fireNature belong
+to the same exclusive set, so they cannot co-exist on the same
+project. The snowNature requires
+waterNature, so snowNature will be disabled on a project that
+is missing waterNature. It
+naturally follows that snowNature cannot be enabled on a project
+with fireNature. The fireNature also doesn't allow the creation of linked resources.
+
+<p>
+<pre>
+ <extension id="fireNature" name="Fire Nature" point="org.eclipse.core.resources.natures">
+ <runtime>
+ <run class="com.xyz.natures.Fire"/>
+ </runtime>
+ <one-of-nature id="com.xyz.stateSet"/>
+ <options allowLinking="false"/>
+ </extension>
+
+ <extension id="waterNature" name="Water Nature" point="org.eclipse.core.resources.natures">
+ <runtime>
+ <run class="com.xyz.natures.Water"/>
+ </runtime>
+ <one-of-nature id="com.xyz.stateSet"/>
+ </extension>
+
+ <extension id="snowNature" name="Snow Nature" point="org.eclipse.core.resources.natures">
+ <runtime>
+ <run class="com.xyz.natures.Snow">
+ <parameter name="installBuilder" value="true"/>
+ </run>
+ </runtime>
+ <requires-nature id="com.xyz.coolplugin.waterNature"/>
+ <builder id="com.xyz.snowMaker"/>
+ </extension>
+</pre>
+</p>
+
+If these extensions were defined in a plug-in with id "com.xyz.coolplugin", the fully qualified name
+of these natures would be "com.xyz.coolplugin.fireNature", "com.xyz.coolplugin.waterNature" and
+"com.xyz.coolplugin.snowNature".
+
+
+
+
+
+
+
+
+ The value of the class attribute must represent an
+implementor of
+<samp>org.eclipse.core.resources.IProjectNature</samp>.
+Nature definitions can be examined using the
+<samp>org.eclipse.core.resources.IProjectNatureDescriptor</samp> interface.
+The descriptor objects can be obtained using the methods
+<samp>getNatureDescriptor(String)</samp> and <samp>getNatureDescriptors()</samp>
+on <samp>org.eclipse.core.resources.IWorkspace</samp>.
+
+
+
+
+
+
+
+
+
+ The platform itself does not have any predefined natures.
+Particular product installs may include natures as required.
+
+
+
+
+
+
+
+
+ Copyright (c) 2002, 2009 IBM Corporation and others.<br>
+All rights reserved. This program and the accompanying materials are made
+available under the terms of the Eclipse Public License v1.0 which
+accompanies
+this distribution, and is available at
+<a
+href="http://www.eclipse.org/legal/epl-v10.html">http://www.eclipse.org/legal/epl-v10.html</a>
+
+
+
+
diff --git a/third_party/patches/mars/org.eclipse.core.resources/schema/refreshProviders.exsd b/third_party/patches/mars/org.eclipse.core.resources/schema/refreshProviders.exsd
new file mode 100644
index 0000000000..b34d044b74
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/schema/refreshProviders.exsd
@@ -0,0 +1,133 @@
+
+
+
+
+
+
+
+
+ The workspace supports a mode where changes that occur in the file system are automatically detected and reconciled with the workspace in memory. By default, this is accomplished by creating a monitor that polls the file system and periodically searching for changes. The monitor factories extension point allows clients to create more efficient monitors, typically by hooking into some native file system facility for change callbacks.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ A human-readable name for the monitor factory
+
+
+
+
+
+
+
+
+
+ The fully qualified name of a class implementing <code>org.eclipse.core.resources.refresh.RefreshProvider</code>.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 3.0
+
+
+
+
+
+
+
+
+ Following is an example of an adapter declaration. This example declares that this plug-in will provide an adapter factory that will adapt objects of type IFile to objects of type MyFile.
+<p>
+<pre>
+ <extension
+ id="coolProvider"
+ point="org.eclipse.core.resources.refreshProviders">
+ <refreshProvider
+ name="Cool Refresh Provider"
+ class="com.xyz.CoolRefreshProvider">
+ </refreshProvider>
+ </extension>
+</pre>
+</p>
+
+
+
+
+
+
+
+
+ Refresh provider implementations must subclass the abstract type <tt>RefreshProvider</tt> in the <tt>org.eclipse.core.resources.refresh</tt> package. Refresh requests and failures should be forward to the provide <tt>IRefreshResult</tt>. Clients must also provide an implementation of <tt>IRefreshMonitor</tt> through which the workspace can request that refresh monitors be uninstalled.
+
+
+
+
+
+
+
+
+ The <tt>org.eclipse.core.resources.win32</tt> fragment provides a native refresh monitor that uses win32 file system notification callbacks. The workspace also supplies a default naive polling-based monitor that can be used for file systems that do not have native refresh callbacks available.
+
+
+
+
+
+
+
+
+ Copyright (c) 2004, 2005 IBM Corporation and others.<br>
+All rights reserved. This program and the accompanying materials are made
+available under the terms of the Eclipse Public License v1.0 which
+accompanies
+this distribution, and is available at
+<a
+href="http://www.eclipse.org/legal/epl-v10.html">http://www.eclipse.org/legal/epl-v10.html</a>
+
+
+
+
diff --git a/third_party/patches/mars/org.eclipse.core.resources/schema/teamHook.exsd b/third_party/patches/mars/org.eclipse.core.resources/schema/teamHook.exsd
new file mode 100644
index 0000000000..60dc9762c5
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/schema/teamHook.exsd
@@ -0,0 +1,120 @@
+
+
+
+
+
+
+
+
+ For providing an implementation of a TeamHook that is used for mechanisms available only to team providers. This extension point tolerates at most one extension.
+
+
+
+
+
+
+
+
+
+
+
+ a fully qualified identifier of the target extension point
+
+
+
+
+
+
+ an optional identifier of the extension instance
+
+
+
+
+
+
+ an optional name of the extension instance
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ the fully-qualified name of a class which subclasses
+<samp>org.eclipse.core.resources.team.TeamHook</samp>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 2.1
+
+
+
+
+
+
+
+
+ The following is an example of using the teamHook extension point.
+(in file plugin.xml)
+<pre>
+ <extension point="org.eclipse.core.resources.teamHook">
+ <teamHook class="org.eclipse.team.internal.TeamHook"/>
+ </extension>
+</pre>
+
+
+
+
+
+
+
+
+ The value of the class attribute must represent a subclass of <tt>org.eclipse.core.resources.team.TeamHook</tt>.
+
+
+
+
+
+
+
+
+ The Team component will generally provide the implementation of the team hook. The extension point should not be used by any other clients.
+
+
+
+
+
+
+
+
+ Copyright (c) 2002, 2005 IBM Corporation and others.<br>
+All rights reserved. This program and the accompanying materials are made
+available under the terms of the Eclipse Public License v1.0 which
+accompanies
+this distribution, and is available at
+<a
+href="http://www.eclipse.org/legal/epl-v10.html">http://www.eclipse.org/legal/epl-v10.html</a>
+
+
+
+
diff --git a/third_party/patches/mars/org.eclipse.core.resources/schema/variableResolvers.exsd b/third_party/patches/mars/org.eclipse.core.resources/schema/variableResolvers.exsd
new file mode 100644
index 0000000000..b32631b164
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/schema/variableResolvers.exsd
@@ -0,0 +1,139 @@
+
+
+
+
+
+
+
+
+ A variable resolver extends the default list of project path variables that can be used to specify the relative location of a linked resource.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ A class dynamically providing the value of the variable.
+
+
+
+
+
+
+
+
+
+ The value of the variable, either as refering another variable or an absolute path.
+
+
+
+
+
+
+ The prefix for supported variables. We could use wildcards in the future.
+
+
+
+
+
+
+
+
+
+
+
+ 3.6
+
+
+
+
+
+
+
+
+ The value of the class attribute must represent a subclass of <tt>org.eclipse.core.resources.PathVariableResolver</tt>.
+
+
+
+
+
+
+
+
+ <p>
+<pre>
+<extension point="org.eclipse.core.resources.variableResolvers">
+ <variableResolver class="org.eclipse.core.internal.resources.projectVariables.EclipseHomeProjectVariable" name="ECLIPSE_HOME">
+ </variableResolver>
+</extension>
+</pre>
+</p>
+
+
+
+
+
+
+
+
+ The <tt>org.eclipse.core.resources</tt> bundle provides resolvers for the following variables: <tt>ECLIPSE_HOME</tt>, <tt>PROJECT_LOC</tt>, <tt>WORKSPACE_LOC</tt>, and <tt>PARENT</tt>
+
+
+
+
+
+
+
+
+ Copyright (c) 2008, 2010 IBM Corporation and others.<br>
+All rights reserved. This program and the accompanying materials are made
+available under the terms of the Eclipse Public License v1.0 which
+accompanies
+this distribution, and is available at
+<a
+href="http://www.eclipse.org/legal/epl-v10.html">http://www.eclipse.org/legal/epl-v10.html</a>
+
+
+
+
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/AbstractDataTree.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/AbstractDataTree.java
new file mode 100644
index 0000000000..55f8277652
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/AbstractDataTree.java
@@ -0,0 +1,330 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.dtree;
+
+import org.eclipse.core.internal.utils.Messages;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * Data trees can be viewed as generic multi-leaf trees. The tree points to a single
+ * rootNode, and each node can contain an arbitrary number of children.
+ *
+ *
Internally, data trees can be either complete trees (DataTree class), or delta
+ * trees (DeltaDataTree class). A DataTree is a stand-alone tree
+ * that contains all its own data. A DeltaDataTree only stores the
+ * differences between itself and its parent tree. This sparse representation allows
+ * the API user to retain chains of delta trees that represent incremental changes to
+ * a system. Using the delta trees, the user can undo changes to a tree by going up to
+ * the parent tree.
+ *
+ *
Both representations of the tree support the same API, so the user of a tree
+ * never needs to know if they're dealing with a complete tree or a chain of deltas.
+ * Delta trees support an extended API of delta operations. See the DeltaDataTree
+ * class for details.
+ *
+ * @see DataTree
+ * @see DeltaDataTree
+ */
+
+public abstract class AbstractDataTree {
+
+ /**
+ * Whether modifications to the given source tree are allowed
+ */
+ private boolean immutable = false;
+
+ /**
+ * Singleton indicating no children
+ */
+ protected static final IPath[] NO_CHILDREN = new IPath[0];
+
+ /**
+ * Creates a new empty tree
+ */
+ public AbstractDataTree() {
+ this.empty();
+ }
+
+ /**
+ * Returns a copy of the receiver, which shares the receiver's
+ * instance variables.
+ */
+ protected AbstractDataTree copy() {
+ AbstractDataTree newTree = this.createInstance();
+ newTree.setImmutable(this.isImmutable());
+ newTree.setRootNode(this.getRootNode());
+ return newTree;
+ }
+
+ /**
+ * Returns a copy of the node subtree rooted at the given key.
+ *
+ */
+ public abstract AbstractDataTreeNode copyCompleteSubtree(IPath key);
+
+ /**
+ * Creates a new child in the tree. If a child with such a name exists,
+ * it is replaced with the new child
+ *
+ * @param parentKey key of parent for new child.
+ * @param localName name for new child.
+ * @exception ObjectNotFoundException
+ * parentKey does not exist in the receiver
+ * @exception RuntimeException
+ * receiver is immutable
+ */
+ public abstract void createChild(IPath parentKey, String localName);
+
+ /**
+ * Creates a new child in the tree. If a child with such a name exists,
+ * it is replaced with the new child
+ *
+ * @param parentKey key of parent for new child.
+ * @param localName name for new child.
+ * @param object the data for the new child
+ * @exception ObjectNotFoundException
+ * parentKey does not exist in the receiver
+ * @exception RuntimeException
+ * receiver is immutable
+ */
+ public abstract void createChild(IPath parentKey, String localName, Object object);
+
+ /**
+ * Creates and returns a new instance of the tree. This is an
+ * implementation of the factory method creational pattern for allowing
+ * abstract methods to create instances.
+ *
+ * @return the new tree.
+ */
+ protected abstract AbstractDataTree createInstance();
+
+ /**
+ * Creates or replaces a subtree in the tree. The parent node must exist.
+ *
+ * @param key key of parent of subtree to create/replace
+ * @param subtree new subtree to add to tree
+ * @exception RuntimeException receiver is immutable
+ */
+ public abstract void createSubtree(IPath key, AbstractDataTreeNode subtree);
+
+ /**
+ * Deletes a child from the tree.
+ *
+ *
Note: this method requires both parentKey and localName,
+ * making it impossible to delete the root node.
+ *
+ * @param parentKey parent of node to delete.
+ * @param localName name of node to delete.
+ * @exception ObjectNotFoundException
+ * a child of parentKey with name localName does not exist in the receiver
+ * @exception RuntimeException
+ * receiver is immutable
+ */
+ public abstract void deleteChild(IPath parentKey, String localName);
+
+ /**
+ * Initializes the receiver so that it is a complete, empty tree. The
+ * result does not represent a delta on another tree. An empty tree is
+ * defined to have a root node with null data and no children.
+ */
+ public abstract void empty();
+
+ /**
+ * Returns the key of a node in the tree.
+ *
+ * @param parentKey
+ * parent of child to retrieve.
+ * @param index
+ * index of the child to retrieve in its parent.
+ * @exception ObjectNotFoundException
+ * parentKey does not exist in the receiver
+ * @exception ArrayIndexOutOfBoundsException
+ * if no child with the given index (runtime exception)
+ */
+ public IPath getChild(IPath parentKey, int index) {
+ /* Get name of given child of the parent */
+ String child = getNameOfChild(parentKey, index);
+ return parentKey.append(child);
+ }
+
+ /**
+ * Returns the number of children of a node
+ *
+ * @param parentKey
+ * key of the node for which we want to retreive the number of children
+ * @exception ObjectNotFoundException
+ * parentKey does not exist in the receiver
+ */
+ public int getChildCount(IPath parentKey) {
+ return getNamesOfChildren(parentKey).length;
+ }
+
+ /**
+ * Returns the keys of all children of a node.
+ *
+ * @param parentKey
+ * key of parent whose children we want to retrieve.
+ * @exception ObjectNotFoundException
+ * parentKey does not exist in the receiver
+ */
+ public IPath[] getChildren(IPath parentKey) {
+ String names[] = getNamesOfChildren(parentKey);
+ int len = names.length;
+ if (len == 0)
+ return NO_CHILDREN;
+ IPath answer[] = new IPath[len];
+
+ for (int i = 0; i < len; i++) {
+ answer[i] = parentKey.append(names[i]);
+ }
+ return answer;
+ }
+
+ /**
+ * Returns the data of a node.
+ *
+ * @param key
+ * key of node for which we want to retrieve data.
+ * @exception ObjectNotFoundException
+ * key does not exist in the receiver
+ */
+ public abstract Object getData(IPath key);
+
+ /**
+ * Returns the local name of a node in the tree
+ *
+ * @param parentKey
+ * parent of node whose name we want to retrieve
+ * @param index
+ * index of node in its parent
+ * @exception ObjectNotFoundException
+ * parentKey does not exist in the receiver
+ * @exception ArrayIndexOutOfBoundsException
+ * if no child with the given index
+ */
+ public String getNameOfChild(IPath parentKey, int index) {
+ String childNames[] = getNamesOfChildren(parentKey);
+ /* Return the requested child as long as its in range */
+ return childNames[index];
+ }
+
+ /**
+ * Returns the local names for the children of a node
+ *
+ * @param parentKey
+ * key of node whose children we want to retrieve
+ * @exception ObjectNotFoundException
+ * parentKey does not exist in the receiver
+ */
+ public abstract String[] getNamesOfChildren(IPath parentKey);
+
+ /**
+ * Returns the root node of the tree.
+ *
+ *
Both subclasses must be able to return their root node. However subclasses
+ * can have different types of root nodes, so this is not enforced as an abstract
+ * method
+ */
+ AbstractDataTreeNode getRootNode() {
+ throw new AbstractMethodError(Messages.dtree_subclassImplement);
+ }
+
+ /**
+ * Handles the case where an attempt was made to modify
+ * the tree when it was in an immutable state. Throws
+ * an unchecked exception.
+ */
+ static void handleImmutableTree() {
+ throw new RuntimeException(Messages.dtree_immutable);
+ }
+
+ /**
+ * Handles the case where an attempt was made to manipulate
+ * an element in the tree that does not exist. Throws an
+ * unchecked exception.
+ */
+ static void handleNotFound(IPath key) {
+ throw new ObjectNotFoundException(NLS.bind(Messages.dtree_notFound, key));
+ }
+
+ /**
+ * Makes the tree immutable
+ */
+ public void immutable() {
+ immutable = true;
+ }
+
+ /**
+ * Returns true if the receiver includes a node with the given key, false
+ * otherwise.
+ *
+ * @param key
+ * key of node to find
+ */
+ public abstract boolean includes(IPath key);
+
+ /**
+ * Returns true if the tree is immutable, and false otherwise.
+ */
+ public boolean isImmutable() {
+ return immutable;
+ }
+
+ /**
+ * Returns an object containing:
+ * - a flag indicating whether the specified node was found
+ * - the data for the node, if it was found
+ * @param key
+ * key of node for which we want to retrieve data.
+ */
+ public abstract DataTreeLookup lookup(IPath key);
+
+ /**
+ * Returns the key of the root node.
+ */
+ public IPath rootKey() {
+ return Path.ROOT;
+ }
+
+ /**
+ * Sets the data of a node.
+ *
+ * @param key
+ * key of node for which to set data
+ * @param data
+ * new data value for node
+ * @exception ObjectNotFoundException
+ * the nodeKey does not exist in the receiver
+ * @exception IllegalArgumentException
+ * receiver is immutable
+ */
+ public abstract void setData(IPath key, Object data);
+
+ /**
+ * Sets the immutable field.
+ */
+ void setImmutable(boolean bool) {
+ immutable = bool;
+ }
+
+ /**
+ * Sets the root node of the tree.
+ *
+ *
Both subclasses must be able to set their root node. However subclasses
+ * can have different types of root nodes, so this is not enforced as an abstract
+ * method
+ */
+ void setRootNode(AbstractDataTreeNode node) {
+ throw new Error(Messages.dtree_subclassImplement);
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/AbstractDataTreeNode.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/AbstractDataTreeNode.java
new file mode 100644
index 0000000000..190542dd45
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/AbstractDataTreeNode.java
@@ -0,0 +1,598 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2014 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * Thomas Wolf (Paranor AG) -- optimize assembleWith
+ *******************************************************************************/
+package org.eclipse.core.internal.dtree;
+
+import org.eclipse.core.internal.utils.Messages;
+import org.eclipse.core.internal.utils.StringPool;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * This class and its subclasses are used to represent nodes of AbstractDataTrees.
+ * Refer to the DataTree API comments for more details.
+ * @see AbstractDataTree
+ */
+public abstract class AbstractDataTreeNode {
+ /**
+ * Singleton indicating no children.
+ */
+ static final AbstractDataTreeNode[] NO_CHILDREN = new AbstractDataTreeNode[0];
+ protected AbstractDataTreeNode children[];
+ protected String name;
+
+ /* Node types for comparison */
+ public static final int T_COMPLETE_NODE = 0;
+ public static final int T_DELTA_NODE = 1;
+ public static final int T_DELETED_NODE = 2;
+ public static final int T_NO_DATA_DELTA_NODE = 3;
+
+ /**
+ * Creates a new data tree node
+ *
+ * @param name
+ * name of new node
+ * @param children
+ * children of the new node
+ */
+ AbstractDataTreeNode(String name, AbstractDataTreeNode[] children) {
+ this.name = name;
+ if (children == null || children.length == 0)
+ this.children = AbstractDataTreeNode.NO_CHILDREN;
+ else
+ this.children = children;
+ }
+
+ /**
+ * Returns a node which if applied to the receiver will produce
+ * the corresponding node in the given parent tree.
+ *
+ * @param myTree tree to which the node belongs
+ * @param parentTree parent tree on which to base backward delta
+ * @param key key of node in its tree
+ */
+ abstract AbstractDataTreeNode asBackwardDelta(DeltaDataTree myTree, DeltaDataTree parentTree, IPath key);
+
+ /**
+ * If this node is a node in a comparison tree, this method reverses
+ * the comparison for this node and all children
+ */
+ AbstractDataTreeNode asReverseComparisonNode(IComparator comparator) {
+ return this;
+ }
+
+ /**
+ * Returns the result of assembling nodes with the given forward delta nodes.
+ * Both arrays must be sorted by name.
+ * The result is sorted by name.
+ * If keepDeleted is true, explicit representations of deletions are kept,
+ * otherwise nodes to be deleted are removed in the result.
+ */
+ static AbstractDataTreeNode[] assembleWith(AbstractDataTreeNode[] oldNodes, AbstractDataTreeNode[] newNodes, boolean keepDeleted) {
+
+ // Optimize the common case where the new list is empty.
+ if (newNodes.length == 0)
+ return oldNodes;
+
+ // Can't just return newNodes if oldNodes has length 0
+ // because newNodes may contain deleted nodes.
+
+ AbstractDataTreeNode[] resultNodes = new AbstractDataTreeNode[oldNodes.length + newNodes.length];
+
+ // do a merge
+ int oldIndex = 0;
+ int newIndex = 0;
+ int resultIndex = 0;
+ while (oldIndex < oldNodes.length && newIndex < newNodes.length) {
+ int log2 = 31 - Integer.numberOfLeadingZeros(oldNodes.length - oldIndex);
+ if (log2 > 1 && (newNodes.length - newIndex) <= (oldNodes.length - oldIndex) / log2) {
+ // We can expect to fare better using binary search. In particular, this will optimize the case of a folder refresh (new linked
+ // folder with many files in a flat hierarchy), where this is called repeatedly, with oldNodes containing the files added so far,
+ // and newNodes containing exactly one new node for the next file to be added. The old algorithm has quadratic performance
+ // (O((n+1)*n/2); number of string comparisons is the dominating component here) in this case; this new algorithm does O(n*log(n))
+ // string comparisons.
+ String key = newNodes[newIndex].name;
+ // Figure out where to insert the next new node.
+ int left = oldIndex;
+ int right = oldNodes.length - 1;
+ boolean found = false;
+ while (left <= right) {
+ int mid = (left + right) / 2;
+ int compare = key.compareTo(oldNodes[mid].name);
+ if (compare < 0) {
+ right = mid - 1;
+ } else if (compare > 0) {
+ left = mid + 1;
+ } else {
+ left = mid;
+ found = true;
+ break;
+ }
+ }
+ // Now copy oldNodes from oldIndex to left-1, insert the new node, increment newIndex
+ int toCopy = left - oldIndex;
+ System.arraycopy(oldNodes, oldIndex, resultNodes, resultIndex, toCopy);
+ resultIndex += toCopy;
+ if (found) {
+ AbstractDataTreeNode node = oldNodes[left++].assembleWith(newNodes[newIndex++]);
+ if (node != null && (!node.isDeleted() || keepDeleted)) {
+ resultNodes[resultIndex++] = node;
+ }
+ } else {
+ AbstractDataTreeNode node = newNodes[newIndex++];
+ if (!node.isDeleted() || keepDeleted) {
+ resultNodes[resultIndex++] = node;
+ }
+ }
+ oldIndex = left;
+ } else {
+ int compare = oldNodes[oldIndex].name.compareTo(newNodes[newIndex].name);
+ if (compare == 0) {
+ AbstractDataTreeNode node = oldNodes[oldIndex++].assembleWith(newNodes[newIndex++]);
+ if (node != null && (!node.isDeleted() || keepDeleted)) {
+ resultNodes[resultIndex++] = node;
+ }
+ } else if (compare < 0) {
+ resultNodes[resultIndex++] = oldNodes[oldIndex++];
+ } else if (compare > 0) {
+ AbstractDataTreeNode node = newNodes[newIndex++];
+ if (!node.isDeleted() || keepDeleted) {
+ resultNodes[resultIndex++] = node;
+ }
+ }
+ }
+ }
+ while (oldIndex < oldNodes.length) {
+ resultNodes[resultIndex++] = oldNodes[oldIndex++];
+ }
+ while (newIndex < newNodes.length) {
+ AbstractDataTreeNode resultNode = newNodes[newIndex++];
+ if (!resultNode.isDeleted() || keepDeleted) {
+ resultNodes[resultIndex++] = resultNode;
+ }
+ }
+
+ // trim size of result
+ if (resultIndex < resultNodes.length) {
+ System.arraycopy(resultNodes, 0, resultNodes = new AbstractDataTreeNode[resultIndex], 0, resultIndex);
+ }
+ return resultNodes;
+ }
+
+ /**
+ * Returns the result of assembling this node with the given forward delta node.
+ */
+ AbstractDataTreeNode assembleWith(AbstractDataTreeNode node) {
+
+ // If not a delta, or if the old node was deleted,
+ // then the new node represents the complete picture.
+ if (!node.isDelta() || this.isDeleted()) {
+ return node;
+ }
+
+ // node must be either a DataDeltaNode or a NoDataDeltaNode
+ if (node.hasData()) {
+ if (this.isDelta()) {
+ // keep deletions because they still need
+ // to hide child nodes in the parent.
+ AbstractDataTreeNode[] assembledChildren = assembleWith(children, node.children, true);
+ return new DataDeltaNode(name, node.getData(), assembledChildren);
+ }
+ // This is a complete picture, so deletions
+ // wipe out the child and are no longer useful
+ AbstractDataTreeNode[] assembledChildren = assembleWith(children, node.children, false);
+ return new DataTreeNode(name, node.getData(), assembledChildren);
+ }
+ if (this.isDelta()) {
+ AbstractDataTreeNode[] assembledChildren = assembleWith(children, node.children, true);
+ if (this.hasData())
+ return new DataDeltaNode(name, this.getData(), assembledChildren);
+ return new NoDataDeltaNode(name, assembledChildren);
+ }
+ AbstractDataTreeNode[] assembledChildren = assembleWith(children, node.children, false);
+ return new DataTreeNode(name, this.getData(), assembledChildren);
+ }
+
+ /**
+ * Returns the result of assembling this node with the given forward delta node.
+ */
+ AbstractDataTreeNode assembleWith(AbstractDataTreeNode node, IPath key, int keyIndex) {
+
+ // leaf case
+ int keyLen = key.segmentCount();
+ if (keyIndex == keyLen) {
+ return assembleWith(node);
+ }
+
+ // non-leaf case
+ int childIndex = indexOfChild(key.segment(keyIndex));
+ if (childIndex >= 0) {
+ AbstractDataTreeNode copy = copy();
+ copy.children[childIndex] = children[childIndex].assembleWith(node, key, keyIndex + 1);
+ return copy;
+ }
+
+ // Child not found. Build up NoDataDeltaNode hierarchy for rest of key
+ // and assemble with that.
+ for (int i = keyLen - 2; i >= keyIndex; --i) {
+ node = new NoDataDeltaNode(key.segment(i), node);
+ }
+ node = new NoDataDeltaNode(name, node);
+ return assembleWith(node);
+ }
+
+ /**
+ * Returns the child with the given local name. The child must exist.
+ */
+ AbstractDataTreeNode childAt(String localName) {
+ AbstractDataTreeNode node = childAtOrNull(localName);
+ if (node != null) {
+ return node;
+ }
+ throw new ObjectNotFoundException(NLS.bind(Messages.dtree_missingChild, localName));
+ }
+
+ /**
+ * Returns the child with the given local name. Returns null if the child
+ * does not exist.
+ *
+ * @param localName
+ * name of child to retrieve
+ */
+ AbstractDataTreeNode childAtOrNull(String localName) {
+ int index = indexOfChild(localName);
+ return index >= 0 ? children[index] : null;
+ }
+
+ /**
+ * Returns the child with the given local name, ignoring case.
+ * If multiple case variants exist, the search will favour real nodes
+ * over deleted nodes. If multiple real nodes are found, the first one
+ * encountered in case order is returned. Returns null if no matching
+ * children are found.
+ *
+ * @param localName name of child to retrieve
+ */
+ AbstractDataTreeNode childAtIgnoreCase(String localName) {
+ AbstractDataTreeNode result = null;
+ for (int i = 0; i < children.length; i++) {
+ if (children[i].getName().equalsIgnoreCase(localName)) {
+ //if we find a deleted child, keep looking for a real child
+ if (children[i].isDeleted())
+ result = children[i];
+ else
+ return children[i];
+ }
+ }
+ return result;
+ }
+
+ /**
+ */
+ protected static AbstractDataTreeNode[] compareWith(AbstractDataTreeNode[] oldNodes, AbstractDataTreeNode[] newNodes, IComparator comparator) {
+
+ int oldLen = oldNodes.length;
+ int newLen = newNodes.length;
+ int oldIndex = 0;
+ int newIndex = 0;
+ AbstractDataTreeNode[] comparedNodes = new AbstractDataTreeNode[oldLen + newLen];
+ int count = 0;
+
+ while (oldIndex < oldLen && newIndex < newLen) {
+ DataTreeNode oldNode = (DataTreeNode) oldNodes[oldIndex];
+ DataTreeNode newNode = (DataTreeNode) newNodes[newIndex];
+ int compare = oldNode.name.compareTo(newNode.name);
+ if (compare < 0) {
+ /* give the client a chance to say whether it should be in the delta */
+ int userComparison = comparator.compare(oldNode.getData(), null);
+ if (userComparison != 0) {
+ comparedNodes[count++] = convertToRemovedComparisonNode(oldNode, userComparison);
+ }
+ ++oldIndex;
+ } else if (compare > 0) {
+ /* give the client a chance to say whether it should be in the delta */
+ int userComparison = comparator.compare(null, newNode.getData());
+ if (userComparison != 0) {
+ comparedNodes[count++] = convertToAddedComparisonNode(newNode, userComparison);
+ }
+ ++newIndex;
+ } else {
+ AbstractDataTreeNode comparedNode = oldNode.compareWith(newNode, comparator);
+ NodeComparison comparison = (NodeComparison) comparedNode.getData();
+
+ /* skip empty comparisons */
+ if (!(comparison.isUnchanged() && comparedNode.size() == 0)) {
+ comparedNodes[count++] = comparedNode;
+ }
+ ++oldIndex;
+ ++newIndex;
+ }
+ }
+ while (oldIndex < oldLen) {
+ DataTreeNode oldNode = (DataTreeNode) oldNodes[oldIndex++];
+
+ /* give the client a chance to say whether it should be in the delta */
+ int userComparison = comparator.compare(oldNode.getData(), null);
+ if (userComparison != 0) {
+ comparedNodes[count++] = convertToRemovedComparisonNode(oldNode, userComparison);
+ }
+ }
+ while (newIndex < newLen) {
+ DataTreeNode newNode = (DataTreeNode) newNodes[newIndex++];
+
+ /* give the client a chance to say whether it should be in the delta */
+ int userComparison = comparator.compare(null, newNode.getData());
+ if (userComparison != 0) {
+ comparedNodes[count++] = convertToAddedComparisonNode(newNode, userComparison);
+ }
+ }
+
+ if (count == 0) {
+ return NO_CHILDREN;
+ }
+ if (count < comparedNodes.length) {
+ System.arraycopy(comparedNodes, 0, comparedNodes = new AbstractDataTreeNode[count], 0, count);
+ }
+ return comparedNodes;
+ }
+
+ /**
+ */
+ protected static AbstractDataTreeNode[] compareWithParent(AbstractDataTreeNode[] nodes, IPath key, DeltaDataTree parent, IComparator comparator) {
+
+ AbstractDataTreeNode[] comparedNodes = new AbstractDataTreeNode[nodes.length];
+ int count = 0;
+ for (int i = 0; i < nodes.length; ++i) {
+ AbstractDataTreeNode node = nodes[i];
+ AbstractDataTreeNode comparedNode = node.compareWithParent(key.append(node.getName()), parent, comparator);
+ NodeComparison comparison = (NodeComparison) comparedNode.getData();
+ // Skip it if it's an empty comparison (and no children).
+ if (!(comparison.isUnchanged() && comparedNode.size() == 0)) {
+ comparedNodes[count++] = comparedNode;
+ }
+ }
+ if (count == 0) {
+ return NO_CHILDREN;
+ }
+ if (count < comparedNodes.length) {
+ System.arraycopy(comparedNodes, 0, comparedNodes = new AbstractDataTreeNode[count], 0, count);
+ }
+ return comparedNodes;
+ }
+
+ abstract AbstractDataTreeNode compareWithParent(IPath key, DeltaDataTree parent, IComparator comparator);
+
+ static AbstractDataTreeNode convertToAddedComparisonNode(AbstractDataTreeNode newNode, int userComparison) {
+ AbstractDataTreeNode[] children = newNode.getChildren();
+ int n = children.length;
+ AbstractDataTreeNode[] convertedChildren;
+ if (n == 0) {
+ convertedChildren = NO_CHILDREN;
+ } else {
+ convertedChildren = new AbstractDataTreeNode[n];
+ for (int i = 0; i < n; ++i) {
+ convertedChildren[i] = convertToAddedComparisonNode(children[i], userComparison);
+ }
+ }
+ return new DataTreeNode(newNode.name, new NodeComparison(null, newNode.getData(), NodeComparison.K_ADDED, userComparison), convertedChildren);
+ }
+
+ static AbstractDataTreeNode convertToRemovedComparisonNode(AbstractDataTreeNode oldNode, int userComparison) {
+ AbstractDataTreeNode[] children = oldNode.getChildren();
+ int n = children.length;
+ AbstractDataTreeNode[] convertedChildren;
+ if (n == 0) {
+ convertedChildren = NO_CHILDREN;
+ } else {
+ convertedChildren = new AbstractDataTreeNode[n];
+ for (int i = 0; i < n; ++i) {
+ convertedChildren[i] = convertToRemovedComparisonNode(children[i], userComparison);
+ }
+ }
+ return new DataTreeNode(oldNode.name, new NodeComparison(oldNode.getData(), null, NodeComparison.K_REMOVED, userComparison), convertedChildren);
+ }
+
+ /**
+ * Returns a copy of the receiver which shares the receiver elements
+ */
+ abstract AbstractDataTreeNode copy();
+
+ /**
+ * Replaces the receiver's children between "from" and "to", with the children
+ * in otherNode starting at "start". This method replaces the Smalltalk
+ * #replaceFrom:to:with:startingAt: method for copying children in data nodes
+ */
+ protected void copyChildren(int from, int to, AbstractDataTreeNode otherNode, int start) {
+ int other = start;
+ for (int i = from; i <= to; i++, other++) {
+ this.children[i] = otherNode.children[other];
+ }
+ }
+
+ /**
+ * Returns an array of the node's children
+ */
+ public AbstractDataTreeNode[] getChildren() {
+ return children;
+ }
+
+ /**
+ * Returns the node's data
+ */
+ Object getData() {
+ throw new AbstractMethodError(Messages.dtree_subclassImplement);
+ }
+
+ /**
+ * return the name of the node
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Returns true if the receiver can carry data, false otherwise.
+ */
+ boolean hasData() {
+ return false;
+ }
+
+ /**
+ * Returns true if the receiver has a child with the given local name,
+ * false otherwise
+ */
+ boolean includesChild(String localName) {
+ return indexOfChild(localName) != -1;
+ }
+
+ /**
+ * Returns the index of the specified child's name in the receiver.
+ */
+ protected int indexOfChild(String localName) {
+ AbstractDataTreeNode[] nodes = this.children;
+ int left = 0;
+ int right = nodes.length - 1;
+ while (left <= right) {
+ int mid = (left + right) / 2;
+ int compare = localName.compareTo(nodes[mid].name);
+ if (compare < 0) {
+ right = mid - 1;
+ } else if (compare > 0) {
+ left = mid + 1;
+ } else {
+ return mid;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Returns true if the receiver represents a deleted node, false otherwise.
+ */
+ boolean isDeleted() {
+ return false;
+ }
+
+ /**
+ * Returns true if the receiver represents delta information,
+ * false if it represents the complete information.
+ */
+ boolean isDelta() {
+ return false;
+ }
+
+ /**
+ * Returns true if the receiver is an empty delta node, false otherwise.
+ */
+ boolean isEmptyDelta() {
+ return false;
+ }
+
+ /**
+ * Returns the local names of the receiver's children.
+ */
+ String[] namesOfChildren() {
+ String names[] = new String[children.length];
+ /* copy child names (Reverse loop optimized) */
+ for (int i = children.length; --i >= 0;)
+ names[i] = children[i].getName();
+ return names;
+ }
+
+ /**
+ * Replaces the child with the given local name.
+ */
+ void replaceChild(String localName, DataTreeNode node) {
+ int i = indexOfChild(localName);
+ if (i >= 0) {
+ children[i] = node;
+ } else {
+ throw new ObjectNotFoundException(NLS.bind(Messages.dtree_missingChild, localName));
+ }
+ }
+
+ /**
+ * Set the node's children
+ */
+ protected void setChildren(AbstractDataTreeNode newChildren[]) {
+ children = newChildren;
+ }
+
+ /**
+ * Set the node's name
+ */
+ void setName(String s) {
+ name = s;
+ }
+
+ /**
+ * Simplifies the given nodes, and answers their replacements.
+ */
+ protected static AbstractDataTreeNode[] simplifyWithParent(AbstractDataTreeNode[] nodes, IPath key, DeltaDataTree parent, IComparator comparer) {
+ int nodeCount = nodes.length;
+ if (nodeCount == 0)
+ return NO_CHILDREN;
+ AbstractDataTreeNode[] simplifiedNodes = new AbstractDataTreeNode[nodeCount];
+ int simplifiedCount = 0;
+ for (int i = 0; i < nodeCount; ++i) {
+ AbstractDataTreeNode node = nodes[i];
+ AbstractDataTreeNode simplifiedNode = node.simplifyWithParent(key.append(node.getName()), parent, comparer);
+ if (!simplifiedNode.isEmptyDelta()) {
+ simplifiedNodes[simplifiedCount++] = simplifiedNode;
+ }
+ }
+ if (simplifiedCount == 0) {
+ return NO_CHILDREN;
+ }
+ if (simplifiedCount < simplifiedNodes.length) {
+ System.arraycopy(simplifiedNodes, 0, simplifiedNodes = new AbstractDataTreeNode[simplifiedCount], 0, simplifiedCount);
+ }
+ return simplifiedNodes;
+ }
+
+ /**
+ * Simplifies the given node, and answers its replacement.
+ */
+ abstract AbstractDataTreeNode simplifyWithParent(IPath key, DeltaDataTree parent, IComparator comparer);
+
+ /**
+ * Returns the number of children of the receiver
+ */
+ int size() {
+ return children.length;
+ }
+
+ /* (non-Javadoc
+ * Method declared on IStringPoolParticipant
+ */
+ public void storeStrings(StringPool set) {
+ name = set.add(name);
+ //copy children pointer in case of concurrent modification
+ AbstractDataTreeNode[] nodes = children;
+ if (nodes != null)
+ for (int i = nodes.length; --i >= 0;)
+ nodes[i].storeStrings(set);
+ }
+
+ /**
+ * Returns a unicode representation of the node. This method is used
+ * for debugging purposes only (no NLS support needed)
+ */
+ @Override
+ public String toString() {
+ return "an AbstractDataTreeNode(" + this.getName() + ") with " + getChildren().length + " children."; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+
+ /**
+ * Returns a constant describing the type of node.
+ */
+ abstract int type();
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataDeltaNode.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataDeltaNode.java
new file mode 100644
index 0000000000..bd8129db8f
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataDeltaNode.java
@@ -0,0 +1,121 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2014 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.dtree;
+
+import org.eclipse.core.runtime.IPath;
+
+/**
+ * A DataDeltaNode contains information that represents the differences
+ * between itself and a node in another tree. Refer to the DeltaDataTree
+ * API and comments for details.
+ *
+ * @see DeltaDataTree
+ */
+public class DataDeltaNode extends DataTreeNode {
+ /**
+ * Creates a node with the given name and data, but with no children.
+ */
+ DataDeltaNode(String name, Object data) {
+ super(name, data);
+ }
+
+ /**
+ * Creates a node with the given name, data and children.
+ */
+ DataDeltaNode(String name, Object data, AbstractDataTreeNode[] children) {
+ super(name, data, children);
+ }
+
+ /**
+ * @see AbstractDataTreeNode#asBackwardDelta(DeltaDataTree, DeltaDataTree, IPath)
+ */
+ @Override
+ AbstractDataTreeNode asBackwardDelta(DeltaDataTree myTree, DeltaDataTree parentTree, IPath key) {
+ AbstractDataTreeNode[] newChildren;
+ if (children.length == 0) {
+ newChildren = NO_CHILDREN;
+ } else {
+ newChildren = new AbstractDataTreeNode[children.length];
+ for (int i = children.length; --i >= 0;) {
+ newChildren[i] = children[i].asBackwardDelta(myTree, parentTree, key.append(children[i].getName()));
+ }
+ }
+ return new DataDeltaNode(name, parentTree.getData(key), newChildren);
+ }
+
+ @Override
+ AbstractDataTreeNode compareWithParent(IPath key, DeltaDataTree parent, IComparator comparator) {
+ AbstractDataTreeNode[] comparedChildren = compareWithParent(children, key, parent, comparator);
+ Object oldData = parent.getData(key);
+ Object newData = data;
+ /* don't compare data of root */
+ int userComparison = 0;
+ if (key != parent.rootKey()) {
+ /* allow client to specify user comparison bits */
+ userComparison = comparator.compare(oldData, newData);
+ }
+ return new DataTreeNode(key.lastSegment(), new NodeComparison(oldData, newData, NodeComparison.K_CHANGED, userComparison), comparedChildren);
+ }
+
+ /**
+ * Creates and returns a new copy of the receiver. Makes a deep copy of
+ * children, but a shallow copy of name and data.
+ */
+ @Override
+ AbstractDataTreeNode copy() {
+ AbstractDataTreeNode[] childrenCopy;
+ if (children.length == 0) {
+ childrenCopy = NO_CHILDREN;
+ } else {
+ childrenCopy = new AbstractDataTreeNode[children.length];
+ System.arraycopy(children, 0, childrenCopy, 0, children.length);
+ }
+ return new DataDeltaNode(name, data, childrenCopy);
+ }
+
+ /**
+ * Returns true if the receiver represents delta information,
+ * false if it represents the complete information.
+ */
+ @Override
+ boolean isDelta() {
+ return true;
+ }
+
+ /**
+ * Simplifies the given node, and answers its replacement.
+ */
+ @Override
+ AbstractDataTreeNode simplifyWithParent(IPath key, DeltaDataTree parent, IComparator comparer) {
+ AbstractDataTreeNode[] simplifiedChildren = simplifyWithParent(children, key, parent, comparer);
+ /* don't compare root nodes */
+ if (!key.isRoot() && comparer.compare(parent.getData(key), data) == 0)
+ return new NoDataDeltaNode(name, simplifiedChildren);
+ return new DataDeltaNode(name, data, simplifiedChildren);
+ }
+
+ /**
+ * Returns a unicode representation of the node. This method is used
+ * for debugging purposes only (no NLS support needed)
+ */
+ @Override
+ public String toString() {
+ return "a DataDeltaNode(" + this.getName() + ") with " + getChildren().length + " children."; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+
+ /**
+ * Returns a constant describing the type of node.
+ */
+ @Override
+ int type() {
+ return T_DELTA_NODE;
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTree.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTree.java
new file mode 100644
index 0000000000..f9474d4af6
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTree.java
@@ -0,0 +1,294 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2014 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.dtree;
+
+import org.eclipse.core.runtime.IPath;
+
+/**
+ * A DataTree represents the complete information of a source tree.
+ * The tree contains all information within its branches, and has no relation to any
+ * other source trees (no parent and no children).
+ */
+public class DataTree extends AbstractDataTree {
+
+ /**
+ * the root node of the tree
+ */
+ private DataTreeNode rootNode;
+
+ /**
+ * Creates a new empty tree
+ */
+ public DataTree() {
+ this.empty();
+ }
+
+ /**
+ * Returns a copy of the node subtree rooted at the given key.
+ *
+ * @param key
+ * Key of subtree to copy
+ */
+ @Override
+ public AbstractDataTreeNode copyCompleteSubtree(IPath key) {
+ DataTreeNode node = findNodeAt(key);
+ if (node == null) {
+ handleNotFound(key);
+ }
+ return copyHierarchy(node);
+ }
+
+ /**
+ * Returns a deep copy of a node and all its children.
+ *
+ * @param node
+ * Node to be copied.
+ */
+ DataTreeNode copyHierarchy(DataTreeNode node) {
+ DataTreeNode newNode;
+ int size = node.size();
+ if (size == 0) {
+ newNode = new DataTreeNode(node.getName(), node.getData());
+ } else {
+ AbstractDataTreeNode[] children = node.getChildren();
+ DataTreeNode[] newChildren = new DataTreeNode[size];
+ for (int i = size; --i >= 0;) {
+ newChildren[i] = this.copyHierarchy((DataTreeNode) children[i]);
+ }
+ newNode = new DataTreeNode(node.getName(), node.getData(), newChildren);
+ }
+
+ return newNode;
+ }
+
+ /**
+ * Creates a new child in the tree.
+ * @see AbstractDataTree#createChild(IPath, String)
+ */
+ @Override
+ public void createChild(IPath parentKey, String localName) {
+ createChild(parentKey, localName, null);
+ }
+
+ /**
+ * Creates a new child in the tree.
+ * @see AbstractDataTree#createChild(IPath, String, Object)
+ */
+ @Override
+ public void createChild(IPath parentKey, String localName, Object data) {
+ DataTreeNode node = findNodeAt(parentKey);
+ if (node == null)
+ handleNotFound(parentKey);
+ if (this.isImmutable())
+ handleImmutableTree();
+ /* If node already exists, replace */
+ if (node.includesChild(localName)) {
+ node.replaceChild(localName, new DataTreeNode(localName, data));
+ } else {
+ this.replaceNode(parentKey, node.copyWithNewChild(localName, new DataTreeNode(localName, data)));
+ }
+ }
+
+ /**
+ * Creates and returns an instance of the receiver. This is an
+ * implementation of the factory method creational pattern for allowing
+ * abstract methods to create instances
+ */
+ @Override
+ protected AbstractDataTree createInstance() {
+ return new DataTree();
+ }
+
+ /**
+ * Creates or replaces a subtree in the tree. The parent node must exist.
+ *
+ * @param key
+ * Key of parent node whose subtree we want to create/replace.
+ * @param subtree
+ * New node to insert into tree.
+ */
+ @Override
+ public void createSubtree(IPath key, AbstractDataTreeNode subtree) {
+
+ // Copy it since destructive mods are allowed on the original
+ // and shouldn't affect this tree.
+ DataTreeNode newNode = copyHierarchy((DataTreeNode) subtree);
+
+ if (this.isImmutable()) {
+ handleImmutableTree();
+ }
+
+ if (key.isRoot()) {
+ setRootNode(newNode);
+ } else {
+ String localName = key.lastSegment();
+ newNode.setName(localName); // Another mod, but it's OK we've already copied
+
+ IPath parentKey = key.removeLastSegments(1);
+
+ DataTreeNode node = findNodeAt(parentKey);
+ if (node == null) {
+ handleNotFound(parentKey);
+ }
+
+ /* If node already exists, replace */
+ if (node.includesChild(localName)) {
+ node.replaceChild(localName, newNode);
+ }
+
+ this.replaceNode(parentKey, node.copyWithNewChild(localName, newNode));
+ }
+ }
+
+ /**
+ * Deletes a child of the tree.
+ * @see AbstractDataTree#deleteChild(IPath, String)
+ */
+ @Override
+ public void deleteChild(IPath parentKey, String localName) {
+ if (this.isImmutable())
+ handleImmutableTree();
+ DataTreeNode node = findNodeAt(parentKey);
+ if (node == null || (!node.includesChild(localName))) {
+ handleNotFound(node == null ? parentKey : parentKey.append(localName));
+ } else {
+ this.replaceNode(parentKey, node.copyWithoutChild(localName));
+ }
+ }
+
+ /**
+ * Initializes the receiver.
+ * @see AbstractDataTree#empty()
+ */
+ @Override
+ public void empty() {
+ this.setRootNode(new DataTreeNode(null, null));
+ }
+
+ /**
+ * Returns the specified node if it is present, otherwise returns null.
+ *
+ * @param key
+ * Key of node to return
+ */
+ public DataTreeNode findNodeAt(IPath key) {
+ AbstractDataTreeNode node = this.getRootNode();
+ int keyLength = key.segmentCount();
+ for (int i = 0; i < keyLength; i++) {
+ try {
+ node = node.childAt(key.segment(i));
+ } catch (ObjectNotFoundException notFound) {
+ return null;
+ }
+ }
+ return (DataTreeNode) node;
+ }
+
+ /**
+ * Returns the data at the specified node.
+ *
+ * @param key
+ * Node whose data to return.
+ */
+ @Override
+ public Object getData(IPath key) {
+ DataTreeNode node = findNodeAt(key);
+ if (node == null) {
+ handleNotFound(key);
+ return null;
+ }
+ return node.getData();
+ }
+
+ /**
+ * Returns the names of the children of a node.
+ * @see AbstractDataTree#getNamesOfChildren(IPath)
+ */
+ @Override
+ public String[] getNamesOfChildren(IPath parentKey) {
+ DataTreeNode parentNode;
+ parentNode = findNodeAt(parentKey);
+ if (parentNode == null) {
+ handleNotFound(parentKey);
+ return null;
+ }
+ return parentNode.namesOfChildren();
+ }
+
+ /**
+ * Returns the root node of the tree
+ */
+ @Override
+ AbstractDataTreeNode getRootNode() {
+ return rootNode;
+ }
+
+ /**
+ * Returns true if the receiver includes a node with
+ * the given key, false otherwise.
+ */
+ @Override
+ public boolean includes(IPath key) {
+ return (findNodeAt(key) != null);
+ }
+
+ /**
+ * Returns an object containing:
+ * - a flag indicating whether the specified node was found
+ * - the data for the node, if it was found
+ * @param key
+ * key of node for which we want to retrieve data.
+ */
+ @Override
+ public DataTreeLookup lookup(IPath key) {
+ DataTreeNode node = this.findNodeAt(key);
+ if (node == null)
+ return DataTreeLookup.newLookup(key, false, null);
+ return DataTreeLookup.newLookup(key, true, node.getData());
+ }
+
+ /**
+ * Replaces the node at the specified key with the given node
+ */
+ protected void replaceNode(IPath key, DataTreeNode node) {
+ DataTreeNode found;
+ if (key.isRoot()) {
+ this.setRootNode(node);
+ } else {
+ found = this.findNodeAt(key.removeLastSegments(1));
+ found.replaceChild(key.lastSegment(), node);
+ }
+ }
+
+ /**
+ * Sets the data at the specified node.
+ * @see AbstractDataTree#setData(IPath, Object)
+ */
+ @Override
+ public void setData(IPath key, Object data) {
+ DataTreeNode node = this.findNodeAt(key);
+ if (this.isImmutable())
+ handleImmutableTree();
+ if (node == null) {
+ handleNotFound(key);
+ } else {
+ node.setData(data);
+ }
+ }
+
+ /**
+ * Sets the root node of the tree
+ * @see AbstractDataTree#setRootNode(AbstractDataTreeNode)
+ */
+ void setRootNode(DataTreeNode aNode) {
+ rootNode = aNode;
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTreeLookup.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTreeLookup.java
new file mode 100644
index 0000000000..f3a8e1df72
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTreeLookup.java
@@ -0,0 +1,80 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2005 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.dtree;
+
+import org.eclipse.core.runtime.IPath;
+
+/**
+ * The result of doing a lookup() in a data tree. Uses an instance
+ * pool that assumes no more than POOL_SIZE instance will ever be
+ * needed concurrently. Reclaims and reuses instances on an LRU basis.
+ */
+public class DataTreeLookup {
+ public IPath key;
+ public boolean isPresent;
+ public Object data;
+ public boolean foundInFirstDelta;
+ private static final int POOL_SIZE = 100;
+ /**
+ * The array of lookup instances available for use.
+ */
+ private static DataTreeLookup[] instancePool;
+ /**
+ * The index of the next available lookup instance.
+ */
+ private static int nextFree = 0;
+ static {
+ instancePool = new DataTreeLookup[POOL_SIZE];
+ //fill the pool with objects
+ for (int i = 0; i < POOL_SIZE; i++) {
+ instancePool[i] = new DataTreeLookup();
+ }
+ }
+
+ /**
+ * Constructors for internal use only. Use factory methods.
+ */
+ private DataTreeLookup() {
+ super();
+ }
+
+ /**
+ * Factory method for creating a new lookup object.
+ */
+ public static DataTreeLookup newLookup(IPath nodeKey, boolean isPresent, Object data) {
+ DataTreeLookup instance;
+ synchronized (instancePool) {
+ instance = instancePool[nextFree];
+ nextFree = ++nextFree % POOL_SIZE;
+ }
+ instance.key = nodeKey;
+ instance.isPresent = isPresent;
+ instance.data = data;
+ instance.foundInFirstDelta = false;
+ return instance;
+ }
+
+ /**
+ * Factory method for creating a new lookup object.
+ */
+ public static DataTreeLookup newLookup(IPath nodeKey, boolean isPresent, Object data, boolean foundInFirstDelta) {
+ DataTreeLookup instance;
+ synchronized (instancePool) {
+ instance = instancePool[nextFree];
+ nextFree = ++nextFree % POOL_SIZE;
+ }
+ instance.key = nodeKey;
+ instance.isPresent = isPresent;
+ instance.data = data;
+ instance.foundInFirstDelta = foundInFirstDelta;
+ return instance;
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTreeNode.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTreeNode.java
new file mode 100644
index 0000000000..4db9d3245a
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTreeNode.java
@@ -0,0 +1,369 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2014 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.dtree;
+
+import org.eclipse.core.internal.utils.*;
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.core.runtime.IPath;
+
+/**
+ * DataTreeNodes are the nodes of a DataTree. Their
+ * information and their subtrees are complete, and do not represent deltas on
+ * another node or subtree.
+ */
+public class DataTreeNode extends AbstractDataTreeNode {
+ protected Object data;
+
+ /**
+ * Creates a new node
+ *
+ * @param name name of node
+ * @param data data for node
+ */
+ public DataTreeNode(String name, Object data) {
+ super(name, AbstractDataTreeNode.NO_CHILDREN);
+ this.data = data;
+ }
+
+ /**
+ * Creates a new node
+ *
+ * @param name name of node
+ * @param data data for node
+ * @param children children for new node
+ */
+ public DataTreeNode(String name, Object data, AbstractDataTreeNode[] children) {
+ super(name, children);
+ this.data = data;
+ }
+
+ /**
+ * @see AbstractDataTreeNode#asBackwardDelta(DeltaDataTree, DeltaDataTree, IPath)
+ */
+ @Override
+ AbstractDataTreeNode asBackwardDelta(DeltaDataTree myTree, DeltaDataTree parentTree, IPath key) {
+ if (parentTree.includes(key))
+ return parentTree.copyCompleteSubtree(key);
+ return new DeletedNode(name);
+ }
+
+ /**
+ * If this node is a node in a comparison tree, this method reverses
+ * the comparison for this node and all children. Returns null
+ * if this node should no longer be included in the comparison tree.
+ */
+ @Override
+ AbstractDataTreeNode asReverseComparisonNode(IComparator comparator) {
+ NodeComparison comparison = null;
+ try {
+ comparison = ((NodeComparison) data).asReverseComparison(comparator);
+ } catch (ClassCastException e) {
+ Assert.isTrue(false, Messages.dtree_reverse);
+ }
+
+ int nextChild = 0;
+ for (int i = 0; i < children.length; i++) {
+ AbstractDataTreeNode child = children[i].asReverseComparisonNode(comparator);
+ if (child != null) {
+ children[nextChild++] = child;
+ }
+ }
+
+ if (nextChild == 0 && comparison.getUserComparison() == 0) {
+ /* no children and no change */
+ return null;
+ }
+
+ /* set the new data */
+ data = comparison;
+
+ /* shrink child array as necessary */
+ if (nextChild < children.length) {
+ AbstractDataTreeNode[] newChildren = new AbstractDataTreeNode[nextChild];
+ System.arraycopy(children, 0, newChildren, 0, nextChild);
+ children = newChildren;
+ }
+
+ return this;
+ }
+
+ AbstractDataTreeNode compareWith(DataTreeNode other, IComparator comparator) {
+ AbstractDataTreeNode[] comparedChildren = compareWith(children, other.children, comparator);
+ Object oldData = data;
+ Object newData = other.data;
+
+ /* don't allow comparison of implicit root node */
+ int userComparison = 0;
+ if (name != null) {
+ userComparison = comparator.compare(oldData, newData);
+ }
+
+ return new DataTreeNode(name, new NodeComparison(oldData, newData, NodeComparison.K_CHANGED, userComparison), comparedChildren);
+ }
+
+ @Override
+ AbstractDataTreeNode compareWithParent(IPath key, DeltaDataTree parent, IComparator comparator) {
+ if (!parent.includes(key))
+ return convertToAddedComparisonNode(this, NodeComparison.K_ADDED);
+ DataTreeNode inParent = (DataTreeNode) parent.copyCompleteSubtree(key);
+ return inParent.compareWith(this, comparator);
+ }
+
+ /**
+ * Creates and returns a new copy of the receiver.
+ */
+ @Override
+ AbstractDataTreeNode copy() {
+ if (children.length > 0) {
+ AbstractDataTreeNode[] childrenCopy = new AbstractDataTreeNode[children.length];
+ System.arraycopy(children, 0, childrenCopy, 0, children.length);
+ return new DataTreeNode(name, data, childrenCopy);
+ }
+ return new DataTreeNode(name, data, children);
+ }
+
+ /**
+ * Returns a new node containing a child with the given local name in
+ * addition to the receiver's current children and data.
+ *
+ * @param localName
+ * name of new child
+ * @param childNode
+ * new child node
+ */
+ DataTreeNode copyWithNewChild(String localName, DataTreeNode childNode) {
+
+ AbstractDataTreeNode[] children = this.children;
+ int left = 0;
+ int right = children.length - 1;
+ while (left <= right) {
+ int mid = (left + right) / 2;
+ int compare = localName.compareTo(children[mid].name);
+ if (compare < 0) {
+ right = mid - 1;
+ } else if (compare > 0) {
+ left = mid + 1;
+ } else {
+ throw new Error(); // it shouldn't have been here yet
+ }
+ }
+
+ AbstractDataTreeNode[] newChildren = new AbstractDataTreeNode[children.length + 1];
+ System.arraycopy(children, 0, newChildren, 0, left);
+ childNode.setName(localName);
+ newChildren[left] = childNode;
+ System.arraycopy(children, left, newChildren, left + 1, children.length - left);
+ return new DataTreeNode(this.getName(), this.getData(), newChildren);
+ }
+
+ /**
+ * Returns a new node without the specified child, but with the rest
+ * of the receiver's current children and its data.
+ *
+ * @param localName
+ * name of child to exclude
+ */
+ DataTreeNode copyWithoutChild(String localName) {
+
+ int index, newSize;
+ DataTreeNode newNode;
+ AbstractDataTreeNode children[];
+
+ index = this.indexOfChild(localName);
+ if (index == -1) {
+ newNode = (DataTreeNode) this.copy();
+ } else {
+ newSize = this.size() - 1;
+ children = new AbstractDataTreeNode[newSize];
+ newNode = new DataTreeNode(this.getName(), this.getData(), children);
+ newNode.copyChildren(0, index - 1, this, 0); //#from:to:with:startingAt:
+ newNode.copyChildren(index, newSize - 1, this, index + 1);
+ }
+ return newNode;
+ }
+
+ /**
+ * Returns an array of delta nodes representing the forward delta between
+ * the given two lists of nodes.
+ * The given nodes must all be complete nodes.
+ */
+ protected static AbstractDataTreeNode[] forwardDeltaWith(AbstractDataTreeNode[] oldNodes, AbstractDataTreeNode[] newNodes, IComparator comparer) {
+ if (oldNodes.length == 0 && newNodes.length == 0) {
+ return NO_CHILDREN;
+ }
+
+ AbstractDataTreeNode[] childDeltas = null;
+ int numChildDeltas = 0;
+ int childDeltaMax = 0;
+
+ // do a merge
+ int oldIndex = 0;
+ int newIndex = 0;
+ while (oldIndex < oldNodes.length && newIndex < newNodes.length) {
+ String oldName = oldNodes[oldIndex].name;
+ String newName = newNodes[newIndex].name;
+ int compare = oldName.compareTo(newName);
+ if (compare == 0) {
+ AbstractDataTreeNode deltaNode = forwardDeltaWithOrNullIfEqual(oldNodes[oldIndex++], newNodes[newIndex++], comparer);
+ if (deltaNode != null) {
+ if (numChildDeltas >= childDeltaMax) {
+ if (childDeltas == null)
+ childDeltas = new AbstractDataTreeNode[childDeltaMax = 5];
+ else
+ System.arraycopy(childDeltas, 0, childDeltas = new AbstractDataTreeNode[childDeltaMax = childDeltaMax * 2 + 1], 0, numChildDeltas);
+ }
+ childDeltas[numChildDeltas++] = deltaNode;
+ }
+ } else if (compare < 0) {
+ if (numChildDeltas >= childDeltaMax) {
+ if (childDeltas == null)
+ childDeltas = new AbstractDataTreeNode[childDeltaMax = 5];
+ else
+ System.arraycopy(childDeltas, 0, childDeltas = new AbstractDataTreeNode[childDeltaMax = childDeltaMax * 2 + 1], 0, numChildDeltas);
+ }
+ childDeltas[numChildDeltas++] = new DeletedNode(oldName);
+ oldIndex++;
+ } else {
+ if (numChildDeltas >= childDeltaMax) {
+ if (childDeltas == null)
+ childDeltas = new AbstractDataTreeNode[childDeltaMax = 5];
+ else
+ System.arraycopy(childDeltas, 0, childDeltas = new AbstractDataTreeNode[childDeltaMax = childDeltaMax * 2 + 1], 0, numChildDeltas);
+ }
+ childDeltas[numChildDeltas++] = newNodes[newIndex++];
+ }
+ }
+ while (oldIndex < oldNodes.length) {
+ if (numChildDeltas >= childDeltaMax) {
+ if (childDeltas == null)
+ childDeltas = new AbstractDataTreeNode[childDeltaMax = 5];
+ else
+ System.arraycopy(childDeltas, 0, childDeltas = new AbstractDataTreeNode[childDeltaMax = childDeltaMax * 2 + 1], 0, numChildDeltas);
+ }
+ childDeltas[numChildDeltas++] = new DeletedNode(oldNodes[oldIndex++].name);
+ }
+ while (newIndex < newNodes.length) {
+ if (numChildDeltas >= childDeltaMax) {
+ if (childDeltas == null)
+ childDeltas = new AbstractDataTreeNode[childDeltaMax = 5];
+ else
+ System.arraycopy(childDeltas, 0, childDeltas = new AbstractDataTreeNode[childDeltaMax = childDeltaMax * 2 + 1], 0, numChildDeltas);
+ }
+ childDeltas[numChildDeltas++] = newNodes[newIndex++];
+ }
+
+ // trim size of result
+ if (numChildDeltas == 0) {
+ return NO_CHILDREN;
+ }
+ if (numChildDeltas < childDeltaMax) {
+ System.arraycopy(childDeltas, 0, childDeltas = new AbstractDataTreeNode[numChildDeltas], 0, numChildDeltas);
+ }
+ return childDeltas;
+ }
+
+ /**
+ * Returns a node representing the forward delta between
+ * the given two (complete) nodes.
+ */
+ protected AbstractDataTreeNode forwardDeltaWith(DataTreeNode other, IComparator comparer) {
+ AbstractDataTreeNode deltaNode = forwardDeltaWithOrNullIfEqual(this, other, comparer);
+ if (deltaNode == null) {
+ return new NoDataDeltaNode(name, NO_CHILDREN);
+ }
+ return deltaNode;
+ }
+
+ /**
+ * Returns a node representing the forward delta between
+ * the given two (complete) nodes, or null if the two nodes are equal.
+ * Although typed as abstract nodes, the given nodes must be complete.
+ */
+ protected static AbstractDataTreeNode forwardDeltaWithOrNullIfEqual(AbstractDataTreeNode oldNode, AbstractDataTreeNode newNode, IComparator comparer) {
+ AbstractDataTreeNode[] childDeltas = forwardDeltaWith(oldNode.children, newNode.children, comparer);
+ Object newData = newNode.getData();
+ if (comparer.compare(oldNode.getData(), newData) == 0) {
+ if (childDeltas.length == 0) {
+ return null;
+ }
+ return new NoDataDeltaNode(newNode.name, childDeltas);
+ }
+ return new DataDeltaNode(newNode.name, newData, childDeltas);
+ }
+
+ /**
+ * Returns the data for the node
+ */
+ @Override
+ public Object getData() {
+ return data;
+ }
+
+ /**
+ * Returns true if the receiver can carry data, false otherwise.
+ */
+ @Override
+ boolean hasData() {
+ return true;
+ }
+
+ /**
+ * Sets the data for the node
+ */
+ void setData(Object o) {
+ data = o;
+ }
+
+ /**
+ * Simplifies the given node, and answers its replacement.
+ */
+ @Override
+ AbstractDataTreeNode simplifyWithParent(IPath key, DeltaDataTree parent, IComparator comparer) {
+ /* If not in parent, can't be simplified */
+ if (!parent.includes(key)) {
+ return this;
+ }
+ /* Can't just call simplify on children since this will miss the case
+ where a child exists in the parent but does not in this.
+ See PR 1FH5RYA. */
+ DataTreeNode parentsNode = (DataTreeNode) parent.copyCompleteSubtree(key);
+ return parentsNode.forwardDeltaWith(this, comparer);
+ }
+
+ /* (non-Javadoc
+ * Method declared on IStringPoolParticipant
+ */
+ @Override
+ public void storeStrings(StringPool set) {
+ super.storeStrings(set);
+ //copy data for thread safety
+ Object o = data;
+ if (o instanceof IStringPoolParticipant)
+ ((IStringPoolParticipant) o).shareStrings(set);
+ }
+
+ /**
+ * Returns a unicode representation of the node. This method is used
+ * for debugging purposes only (no NLS support needed)
+ */
+ @Override
+ public String toString() {
+ return "a DataTreeNode(" + this.getName() + ") with " + getChildren().length + " children."; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+
+ /**
+ * Returns a constant describing the type of node.
+ */
+ @Override
+ int type() {
+ return T_COMPLETE_NODE;
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTreeReader.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTreeReader.java
new file mode 100644
index 0000000000..faecd3f0c3
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTreeReader.java
@@ -0,0 +1,150 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * Francis Lynch (Wind River) - [305718] Allow reading snapshot into renamed project
+ *******************************************************************************/
+package org.eclipse.core.internal.dtree;
+
+import java.io.DataInput;
+import java.io.IOException;
+import org.eclipse.core.internal.utils.Messages;
+import org.eclipse.core.runtime.*;
+
+/**
+ * Class used for reading a single data tree (no parents) from an input stream
+ */
+public class DataTreeReader {
+ /**
+ * Callback for reading tree data
+ */
+ protected IDataFlattener flatener;
+
+ /**
+ * The stream to read the tree from
+ */
+ protected DataInput input;
+
+ /**
+ * Creates a new DeltaTreeReader.
+ */
+ public DataTreeReader(IDataFlattener f) {
+ flatener = f;
+ }
+
+ /**
+ * Returns true if the given node type has data.
+ */
+ protected boolean hasData(int nodeType) {
+ switch (nodeType) {
+ case AbstractDataTreeNode.T_COMPLETE_NODE :
+ case AbstractDataTreeNode.T_DELTA_NODE :
+ return true;
+ case AbstractDataTreeNode.T_DELETED_NODE :
+ case AbstractDataTreeNode.T_NO_DATA_DELTA_NODE :
+ default :
+ return false;
+ }
+ }
+
+ /**
+ * Reads a node from the given input stream.
+ * If newProjectName is non-empty, use it for the name of
+ * the project (first node under root) in the created node
+ * instead of the name read from the stream.
+ */
+ protected AbstractDataTreeNode readNode(IPath parentPath, String newProjectName) throws IOException {
+ /* read the node name */
+ String name = input.readUTF();
+
+ /* read the node type */
+ int nodeType = readNumber();
+
+ /* maybe read the data */
+ IPath path;
+
+ /* if not the root node */
+ if (parentPath != null) {
+ if (parentPath.equals(Path.ROOT) &&
+ newProjectName.length() > 0 && name.length() > 0) {
+ /* use the supplied name for the project node */
+ name = newProjectName;
+ }
+ path = parentPath.append(name);
+ } else {
+ path = Path.ROOT;
+ }
+
+ Object data = null;
+ if (hasData(nodeType)) {
+
+ /* read flag indicating if the data is null */
+ int dataFlag = readNumber();
+ if (dataFlag != 0) {
+ data = flatener.readData(path, input);
+ }
+ }
+
+ /* read the number of children */
+ int childCount = readNumber();
+
+ /* read the children */
+ AbstractDataTreeNode[] children;
+ if (childCount == 0) {
+ children = AbstractDataTreeNode.NO_CHILDREN;
+ } else {
+ children = new AbstractDataTreeNode[childCount];
+ for (int i = 0; i < childCount; i++) {
+ children[i] = readNode(path, newProjectName);
+ }
+ }
+
+ /* create the appropriate node */
+ switch (nodeType) {
+ case AbstractDataTreeNode.T_COMPLETE_NODE :
+ return new DataTreeNode(name, data, children);
+ case AbstractDataTreeNode.T_DELTA_NODE :
+ return new DataDeltaNode(name, data, children);
+ case AbstractDataTreeNode.T_DELETED_NODE :
+ return new DeletedNode(name);
+ case AbstractDataTreeNode.T_NO_DATA_DELTA_NODE :
+ return new NoDataDeltaNode(name, children);
+ default :
+ Assert.isTrue(false, Messages.dtree_switchError);
+ return null;
+ }
+ }
+
+ /**
+ * Reads an integer stored in compact format. Numbers between
+ * 0 and 254 inclusive occupy 1 byte; other numbers occupy 5 bytes,
+ * the first byte being 0xff and the next 4 bytes being the standard
+ * representation of an int.
+ */
+ protected int readNumber() throws IOException {
+ byte b = input.readByte();
+ int number = (b & 0xff); // not a no-op! converts unsigned byte to int
+
+ if (number == 0xff) { // magic escape value
+ number = input.readInt();
+ }
+ return number;
+ }
+
+ /**
+ * Reads a DeltaDataTree from the given input stream.
+ * If newProjectName is non-empty, use it for the name of
+ * the project (first node under root) in the returned tree
+ * instead of the name read from the stream.
+ */
+ public DeltaDataTree readTree(DeltaDataTree parent, DataInput input, String newProjectName) throws IOException {
+ this.input = input;
+ AbstractDataTreeNode root = readNode(Path.ROOT, newProjectName);
+ return new DeltaDataTree(root, parent);
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTreeWriter.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTreeWriter.java
new file mode 100644
index 0000000000..f8755caafa
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTreeWriter.java
@@ -0,0 +1,184 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2005 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.dtree;
+
+import java.io.DataOutput;
+import java.io.IOException;
+import org.eclipse.core.runtime.*;
+
+/**
+ * Class for writing a single data tree (no parents) to an output stream.
+ */
+public class DataTreeWriter {
+ /**
+ * Callback for serializing tree data
+ */
+ protected IDataFlattener flatener;
+
+ /**
+ * The stream to write output to
+ */
+ protected DataOutput output;
+
+ /**
+ * Constant representing infinite recursion depth
+ */
+ public static final int D_INFINITE = -1;
+
+ /**
+ * Creates a new DeltaTreeWriter.
+ */
+ public DataTreeWriter(IDataFlattener f) {
+ flatener = f;
+ }
+
+ /**
+ * Writes the subtree rooted at the given node.
+ * @param node The subtree to write.
+ * @param path The path of the current node.
+ * @param depth The depth of the subtree to write.
+ */
+ protected void writeNode(AbstractDataTreeNode node, IPath path, int depth) throws IOException {
+ int type = node.type();
+
+ /* write the node name */
+ String name = node.getName();
+ if (name == null) {
+ name = ""; //$NON-NLS-1$
+ }
+ output.writeUTF(name);
+
+ /* write the node type */
+ writeNumber(type);
+
+ /* maybe write the data */
+ if (node.hasData()) {
+ Object data = node.getData();
+
+ /**
+ * Write a flag indicating whether or not the data field is null.
+ * Zero means data is null, non-zero means data is present
+ */
+ if (data == null) {
+ writeNumber(0);
+ } else {
+ writeNumber(1);
+ flatener.writeData(path, node.getData(), output);
+ }
+
+ }
+
+ /* maybe write the children */
+ if (depth > 0 || depth == D_INFINITE) {
+ AbstractDataTreeNode[] children = node.getChildren();
+
+ /* write the number of children */
+ writeNumber(children.length);
+
+ /* write the children */
+ int newDepth = (depth == D_INFINITE) ? D_INFINITE : depth - 1;
+ for (int i = 0, imax = children.length; i < imax; i++) {
+ writeNode(children[i], path.append(children[i].getName()), newDepth);
+ }
+ } else {
+ /* write the number of children */
+ writeNumber(0);
+ }
+ }
+
+ /**
+ * Writes an integer in a compact format biased towards
+ * small non-negative numbers. Numbers between
+ * 0 and 254 inclusive occupy 1 byte; other numbers occupy 5 bytes.
+ */
+ protected void writeNumber(int number) throws IOException {
+ if (number >= 0 && number < 0xff) {
+ output.writeByte(number);
+ } else {
+ output.writeByte(0xff);
+ output.writeInt(number);
+ }
+ }
+
+ /**
+ * Writes a single node to the output. Does not recurse
+ * on child nodes, and does not write the number of children.
+ */
+ protected void writeSingleNode(AbstractDataTreeNode node, IPath path) throws IOException {
+ /* write the node name */
+ String name = node.getName();
+ if (name == null) {
+ name = ""; //$NON-NLS-1$
+ }
+ output.writeUTF(name);
+
+ /* write the node type */
+ writeNumber(node.type());
+
+ /* maybe write the data */
+ if (node.hasData()) {
+ Object data = node.getData();
+
+ /**
+ * Write a flag indicating whether or not the data field is null.
+ * Zero means data is null, non-zero means data is present
+ */
+ if (data == null) {
+ writeNumber(0);
+ } else {
+ writeNumber(1);
+ flatener.writeData(path, node.getData(), output);
+ }
+ }
+ }
+
+ /**
+ * Writes the given AbstractDataTree to the given stream. This
+ * writes a single DataTree or DeltaDataTree, ignoring parent
+ * trees.
+ *
+ * @param path Only writes data for the subtree rooted at the given path, and
+ * for all nodes directly between the root and the subtree.
+ * @param depth In the subtree rooted at the given path,
+ * only write up to this depth. A depth of infinity is given
+ * by the constant D_INFINITE.
+ */
+ public void writeTree(AbstractDataTree tree, IPath path, int depth, DataOutput output) throws IOException {
+ this.output = output;
+ /* tunnel down relevant path */
+ AbstractDataTreeNode node = tree.getRootNode();
+ IPath currentPath = Path.ROOT;
+ String[] segments = path.segments();
+ for (int i = 0; i < segments.length; i++) {
+ String nextSegment = segments[i];
+
+ /* write this node to the output */
+ writeSingleNode(node, currentPath);
+
+ currentPath = currentPath.append(nextSegment);
+ node = node.childAtOrNull(nextSegment);
+
+ /* write the number of children for this node */
+ if (node != null) {
+ writeNumber(1);
+ } else {
+ /* can't navigate down the path, just give up with what we have so far */
+ writeNumber(0);
+ return;
+ }
+ }
+
+ Assert.isTrue(currentPath.equals(path), "dtree.navigationError"); //$NON-NLS-1$
+
+ /* recursively write the subtree we're interested in */
+ writeNode(node, path, depth);
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DeletedNode.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DeletedNode.java
new file mode 100644
index 0000000000..cd8445dcc3
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DeletedNode.java
@@ -0,0 +1,131 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2014 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.dtree;
+
+import org.eclipse.core.internal.utils.Messages;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * A DeletedNode represents a node that has been deleted in a
+ * DeltaDataTree. It is a node that existed in the parent tree,
+ * but no longer exists in the current delta tree. It has no children or data.
+ */
+public class DeletedNode extends AbstractDataTreeNode {
+
+ /**
+ * Creates a new tree with the given name
+ */
+ DeletedNode(String localName) {
+ super(localName, NO_CHILDREN);
+ }
+
+ /**
+ * @see AbstractDataTreeNode#asBackwardDelta(DeltaDataTree, DeltaDataTree, IPath)
+ */
+ @Override
+ AbstractDataTreeNode asBackwardDelta(DeltaDataTree myTree, DeltaDataTree parentTree, IPath key) {
+ if (parentTree.includes(key))
+ return parentTree.copyCompleteSubtree(key);
+ return this;
+ }
+
+ /**
+ * Returns the child with the given local name
+ */
+ @Override
+ AbstractDataTreeNode childAt(String localName) {
+ /* deleted nodes do not have children */
+ throw new ObjectNotFoundException(NLS.bind(Messages.dtree_missingChild, localName));
+ }
+
+ /**
+ * Returns the child with the given local name
+ */
+ @Override
+ AbstractDataTreeNode childAtOrNull(String localName) {
+ /* deleted nodes do not have children */
+ return null;
+ }
+
+ @Override
+ AbstractDataTreeNode compareWithParent(IPath key, DeltaDataTree parent, IComparator comparator) {
+ /**
+ * Just because there is a deleted node, it doesn't mean there must
+ * be a corresponding node in the parent. Deleted nodes can live
+ * in isolation.
+ */
+ if (parent.includes(key))
+ return convertToRemovedComparisonNode(parent.copyCompleteSubtree(key), NodeComparison.K_REMOVED);
+ // Node doesn't exist in either tree. Return an empty comparison.
+ // Empty comparisons are omitted from the delta.
+ return new DataTreeNode(key.lastSegment(), new NodeComparison(null, null, 0, 0));
+ }
+
+ /**
+ * Creates and returns a new copy of the receiver. Makes a deep copy of
+ * children, but a shallow copy of name and data.
+ */
+ @Override
+ AbstractDataTreeNode copy() {
+ return new DeletedNode(name);
+ }
+
+ /**
+ * Returns true if the receiver represents a deleted node, false otherwise.
+ */
+ @Override
+ boolean isDeleted() {
+ return true;
+ }
+
+ /**
+ * Simplifies the given node, and returns its replacement.
+ */
+ @Override
+ AbstractDataTreeNode simplifyWithParent(IPath key, DeltaDataTree parent, IComparator comparer) {
+ if (parent.includes(key))
+ return this;
+ return new NoDataDeltaNode(name);
+ }
+
+ /**
+ * Returns the number of children of the receiver
+ */
+ @Override
+ int size() {
+ /* deleted nodes have no children */
+ return 0;
+ }
+
+ /**
+ * Return a unicode representation of the node. This method
+ * is used for debugging purposes only (no NLS please)
+ */
+ @Override
+ public String toString() {
+ return "a DeletedNode(" + this.getName() + ")"; //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ /**
+ * Returns a string describing the type of node.
+ */
+ @Override
+ int type() {
+ return T_DELETED_NODE;
+ }
+
+ @Override
+ AbstractDataTreeNode childAtIgnoreCase(String localName) {
+ /* deleted nodes do not have children */
+ return null;
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DeltaDataTree.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DeltaDataTree.java
new file mode 100644
index 0000000000..097a4ab0dc
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DeltaDataTree.java
@@ -0,0 +1,977 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2014 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.dtree;
+
+import org.eclipse.core.internal.utils.Messages;
+import org.eclipse.core.internal.utils.StringPool;
+import org.eclipse.core.runtime.*;
+
+/**
+ * Externally, a DeltaDataTree appears to have the same content as
+ * a standard data tree. Internally, the delta tree may be complete, or it may
+ * just indicate the changes between itself and its parent.
+ *
+ *
Nodes that exist in the parent but do not exist in the delta, are represented
+ * as instances of DeletedNode. Nodes that are identical in the parent
+ * and the delta, but have differences in their subtrees, are represented as
+ * instances of NoDataDeltaNode in the delta tree. Nodes that differ
+ * between parent and delta are instances of DataDeltaNode. However,
+ * the DataDeltaNode only contains the children whose subtrees differ
+ * between parent and delta.
+ *
+ * A delta tree algebra is used to manipulate sets of delta trees. Given two trees,
+ * one can obtain the delta between the two using the method
+ * forwardDeltaWith(aTree). Given a tree and a delta, one can assemble
+ * the complete tree that the delta represents using the method
+ * assembleWithForwardDelta. Refer to the public API methods of this class
+ * for further details.
+ */
+
+public class DeltaDataTree extends AbstractDataTree {
+ private AbstractDataTreeNode rootNode;
+ private DeltaDataTree parent;
+
+ /**
+ * Creates a new empty tree.
+ */
+ public DeltaDataTree() {
+ this.empty();
+ }
+
+ /**
+ * Creates a new tree.
+ * @param rootNode
+ * root node of new tree.
+ */
+ public DeltaDataTree(AbstractDataTreeNode rootNode) {
+ this.rootNode = rootNode;
+ this.parent = null;
+ }
+
+ protected DeltaDataTree(AbstractDataTreeNode rootNode, DeltaDataTree parent) {
+ this.rootNode = rootNode;
+ this.parent = parent;
+ }
+
+ /**
+ * Adds a child to the tree.
+ *
+ * @param parentKey parent for new child.
+ * @param localName name of child.
+ * @param childNode child node.
+ */
+ protected void addChild(IPath parentKey, String localName, AbstractDataTreeNode childNode) {
+ if (!includes(parentKey))
+ handleNotFound(parentKey);
+ childNode.setName(localName);
+ this.assembleNode(parentKey, new NoDataDeltaNode(parentKey.lastSegment(), childNode));
+ }
+
+ /**
+ * Returns the tree as a backward delta. If the delta is applied to the tree it
+ * will produce its parent. The receiver must have a forward
+ * delta representation. I.e.: Call the receiver's parent A,
+ * and the receiver B. The receiver's representation is A->B.
+ * Returns the delta A<-B. The result is equivalent to A, but has B as its parent.
+ */
+ DeltaDataTree asBackwardDelta() {
+ if (getParent() == null)
+ return newEmptyDeltaTree();
+ return new DeltaDataTree(getRootNode().asBackwardDelta(this, getParent(), rootKey()), this);
+ }
+
+ /**
+ * This method can only be called on a comparison tree created
+ * using DeltaDataTree.compareWith(). This method flips the orientation
+ * of the given comparison tree, so that additions become removals,
+ * and vice-versa. This method destructively changes the tree
+ * as opposed to making a copy.
+ */
+ public DeltaDataTree asReverseComparisonTree(IComparator comparator) {
+ /* don't reverse the root node if it's the absolute root (name==null) */
+ if (rootNode.getName() == null) {
+ AbstractDataTreeNode[] children = rootNode.getChildren();
+ int nextChild = 0;
+ for (int i = 0; i < children.length; i++) {
+ AbstractDataTreeNode newChild = children[i].asReverseComparisonNode(comparator);
+ if (newChild != null) {
+ children[nextChild++] = newChild;
+ }
+ }
+
+ if (nextChild < children.length) {
+ AbstractDataTreeNode[] newChildren = new AbstractDataTreeNode[nextChild];
+ System.arraycopy(children, 0, newChildren, 0, nextChild);
+ rootNode.setChildren(newChildren);
+ }
+ } else {
+ rootNode.asReverseComparisonNode(comparator);
+ }
+ return this;
+ }
+
+ /**
+ * Replaces a node in the tree with the result of assembling the node
+ * with the given delta node (which represents a forward delta on
+ * the existing node).
+ *
+ * @param key key of the node to replace.
+ * @param deltaNode delta node to use to assemble the new node.
+ */
+ protected void assembleNode(IPath key, AbstractDataTreeNode deltaNode) {
+ rootNode = rootNode.assembleWith(deltaNode, key, 0);
+ }
+
+ /**
+ * Assembles the receiver with the given delta tree and answer
+ * the resulting, mutable source tree. The given delta tree must be a
+ * forward delta based on the receiver (i.e. missing information is taken from
+ * the receiver). This operation is used to coalesce delta trees.
+ *
+ *
In detail, suppose that c is a forward delta over source tree a.
+ * Let d := a assembleWithForwardDelta: c.
+ * d has the same content as c, and is represented as a delta tree
+ * whose parent is the same as a's parent.
+ *
+ *
In general, if c is represented as a chain of deltas of length n,
+ * then d is represented as a chain of length n-1.
+ *
+ *
So if a is a complete tree (i.e., has no parent, length=0), then d
+ * will be a complete tree too.
+ *
+ *
Corollary: (a assembleWithForwardDelta: (a forwardDeltaWith: b)) = b
+ */
+ public DeltaDataTree assembleWithForwardDelta(DeltaDataTree deltaTree) {
+ return new DeltaDataTree(getRootNode().assembleWith(deltaTree.getRootNode()), this);
+ }
+
+ /**
+ * Compares this tree with another tree, starting from the given path. The
+ * given path will be the root node of the returned tree. Both this
+ * tree and other tree must contain the given path.
+ */
+ protected DeltaDataTree basicCompare(DeltaDataTree other, IComparator comparator, IPath path) {
+ DeltaDataTree newTree;
+ if (this == other) {
+ newTree = new DeltaDataTree();
+ newTree.setData(Path.ROOT, new NodeComparison(null, null, 0, 0));
+ } else if (other.hasAncestor(this)) {
+ AbstractDataTreeNode assembled = other.searchNodeAt(path);
+ DeltaDataTree tree = other;
+
+ /* Iterate through the receiver's ancestors until the receiver is reached */
+ while ((tree = tree.getParent()) != this) {
+ //ancestor may not contain the given path
+ AbstractDataTreeNode treeNode = tree.searchNodeAt(path);
+ if (treeNode != null) {
+ assembled = treeNode.assembleWith(assembled);
+ }
+ }
+ AbstractDataTreeNode comparedRoot = assembled.compareWithParent(path, this, comparator);
+ newTree = new DeltaDataTree(comparedRoot);
+ } else if (this.hasAncestor(other)) {
+ AbstractDataTreeNode assembled = this.asBackwardDelta().searchNodeAt(path);
+ DeltaDataTree tree = this;
+
+ /* Iterate through the receiver's ancestors until the other tree is reached */
+ while ((tree = tree.getParent()) != other) {
+ assembled = assembled.assembleWith(tree.asBackwardDelta().searchNodeAt(path));
+ }
+ AbstractDataTreeNode comparedRoot = assembled.compareWithParent(path, this, comparator);
+ newTree = new DeltaDataTree(comparedRoot);
+ } else {
+ //revert to naive comparison
+ DataTreeNode thisCompleteRoot = (DataTreeNode) this.copyCompleteSubtree(path);
+ DataTreeNode otherCompleteRoot = (DataTreeNode) other.copyCompleteSubtree(path);
+ AbstractDataTreeNode comparedRoot = thisCompleteRoot.compareWith(otherCompleteRoot, comparator);
+ newTree = new DeltaDataTree(comparedRoot);
+ }
+ newTree.immutable();
+ return newTree;
+ }
+
+ /**
+ * Collapses this tree so that the given ancestor becomes its
+ * immediate parent. Afterwards, this tree will still have exactly the
+ * same contents, but its internal structure will be compressed.
+ *
+ *
This operation should be used to collapse chains of
+ * delta trees that don't contain interesting intermediate states.
+ *
+ *
This is a destructive operation, since it modifies the structure of this
+ * tree instance. This tree must be immutable at the start of this operation,
+ * and will be immutable afterwards.
+ * @return this tree.
+ */
+ public DeltaDataTree collapseTo(DeltaDataTree collapseTo, IComparator comparator) {
+ if (this == collapseTo || getParent() == collapseTo) {
+ //already collapsed
+ return this;
+ }
+ //collapse my tree to be a forward delta of the parent's tree.
+ //c will have the same content as this tree, but its parent will be "parent".
+ DeltaDataTree c = collapseTo.forwardDeltaWith(this, comparator);
+
+ //update my internal root node and parent pointers.
+ this.parent = collapseTo;
+ this.rootNode = c.rootNode;
+ return this;
+ }
+
+ /**
+ * Returns a DeltaDataTree that describes the differences between
+ * this tree and "other" tree. Each node of the returned tree
+ * will contain a NodeComparison object that describes the differences
+ * between the two trees.
+ */
+ public DeltaDataTree compareWith(DeltaDataTree other, IComparator comparator) {
+
+ DeltaDataTree newTree;
+ if (this == other) {
+ newTree = new DeltaDataTree();
+ newTree.setData(Path.ROOT, new NodeComparison(null, null, 0, 0));
+ } else if (other.hasAncestor(this)) {
+
+ AbstractDataTreeNode assembled = other.getRootNode();
+ DeltaDataTree tree = other;
+
+ /* Iterate through the receiver's ancestors until the receiver is reached */
+ while ((tree = tree.getParent()) != this) {
+ assembled = tree.getRootNode().assembleWith(assembled);
+ }
+ AbstractDataTreeNode comparedRoot = assembled.compareWithParent(rootKey(), this, comparator);
+ newTree = new DeltaDataTree(comparedRoot);
+ } else if (this.hasAncestor(other)) {
+ AbstractDataTreeNode assembled = this.asBackwardDelta().getRootNode();
+ DeltaDataTree tree = this;
+
+ /* Iterate through the receiver's ancestors until the other tree is reached */
+ while ((tree = tree.getParent()) != other) {
+ assembled = assembled.assembleWith(tree.asBackwardDelta().getRootNode());
+ }
+ AbstractDataTreeNode comparedRoot = assembled.compareWithParent(rootKey(), this, comparator);
+ newTree = new DeltaDataTree(comparedRoot);
+ } else {
+ //revert to naive comparison if trees have no common ancestry
+ DataTreeNode thisCompleteRoot = (DataTreeNode) this.copyCompleteSubtree(rootKey());
+ DataTreeNode otherCompleteRoot = (DataTreeNode) other.copyCompleteSubtree(rootKey());
+ AbstractDataTreeNode comparedRoot = thisCompleteRoot.compareWith(otherCompleteRoot, comparator);
+ newTree = new DeltaDataTree(comparedRoot);
+ }
+ newTree.immutable();
+ return newTree;
+ }
+
+ /**
+ * Compares this tree with another tree, starting from the given path. The
+ * given path will be the root node of the returned tree.
+ */
+ public DeltaDataTree compareWith(DeltaDataTree other, IComparator comparator, IPath path) {
+ /* need to figure out if trees really contain the given path */
+ if (this.includes(path)) {
+ if (other.includes(path))
+ return basicCompare(other, comparator, path);
+ /* only exists in this tree */
+ return new DeltaDataTree(AbstractDataTreeNode.convertToRemovedComparisonNode(this.copyCompleteSubtree(path), comparator.compare(this.getData(path), null)));
+ }
+ if (other.includes(path))
+ /* only exists in other tree */
+ return new DeltaDataTree(AbstractDataTreeNode.convertToAddedComparisonNode(other.copyCompleteSubtree(path), comparator.compare(null, other.getData(path))));
+ /* doesn't exist in either tree */
+ return DeltaDataTree.createEmptyDelta();
+ }
+
+ /**
+ * Returns a copy of the tree which shares its instance variables.
+ */
+ @Override
+ protected AbstractDataTree copy() {
+ return new DeltaDataTree(rootNode, parent);
+ }
+
+ /**
+ * Returns a complete node containing the contents of a subtree of the tree.
+ *
+ * @param key
+ * key of subtree to copy
+ */
+ @Override
+ public AbstractDataTreeNode copyCompleteSubtree(IPath key) {
+ AbstractDataTreeNode node = searchNodeAt(key);
+ if (node == null) {
+ handleNotFound(key);
+ return null;
+ }
+ if (node.isDelta())
+ return naiveCopyCompleteSubtree(key);
+ //copy the node in case the user wants to hammer the subtree name
+ return node.copy();
+ }
+
+ /**
+ * @see AbstractDataTree#createChild(IPath, String)
+ */
+ @Override
+ public void createChild(IPath parentKey, String localName) {
+ createChild(parentKey, localName, null);
+ }
+
+ /**
+ * @see AbstractDataTree#createChild(IPath, String, Object)
+ */
+ @Override
+ public void createChild(IPath parentKey, String localName, Object data) {
+ if (isImmutable())
+ handleImmutableTree();
+ addChild(parentKey, localName, new DataTreeNode(localName, data));
+ }
+
+ /**
+ * Returns a delta data tree that represents an empty delta.
+ * (i.e. it represents a delta on another (unspecified) tree,
+ * but introduces no changes).
+ */
+ static DeltaDataTree createEmptyDelta() {
+
+ DeltaDataTree newTree = new DeltaDataTree();
+ newTree.emptyDelta();
+ return newTree;
+ }
+
+ /**
+ * Creates and returns an instance of the receiver.
+ * @see AbstractDataTree#createInstance()
+ */
+ @Override
+ protected AbstractDataTree createInstance() {
+ return new DeltaDataTree();
+ }
+
+ /**
+ * @see AbstractDataTree#createSubtree(IPath, AbstractDataTreeNode)
+ */
+ @Override
+ public void createSubtree(IPath key, AbstractDataTreeNode node) {
+ if (isImmutable())
+ handleImmutableTree();
+ if (key.isRoot()) {
+ setParent(null);
+ setRootNode(node);
+ } else {
+ addChild(key.removeLastSegments(1), key.lastSegment(), node);
+ }
+ }
+
+ /**
+ * @see AbstractDataTree#deleteChild(IPath, String)
+ */
+ @Override
+ public void deleteChild(IPath parentKey, String localName) {
+ if (isImmutable())
+ handleImmutableTree();
+ /* If the child does not exist */
+ IPath childKey = parentKey.append(localName);
+ if (!includes(childKey))
+ handleNotFound(childKey);
+ assembleNode(parentKey, new NoDataDeltaNode(parentKey.lastSegment(), new DeletedNode(localName)));
+ }
+
+ /**
+ * Initializes the receiver so that it is a complete, empty tree.
+ * @see AbstractDataTree#empty()
+ */
+ @Override
+ public void empty() {
+ rootNode = new DataTreeNode(null, null);
+ parent = null;
+ }
+
+ /**
+ * Initializes the receiver so that it represents an empty delta.
+ * (i.e. it represents a delta on another (unspecified) tree,
+ * it introduces no changes). The parent is left unchanged.
+ */
+ void emptyDelta() {
+ rootNode = new NoDataDeltaNode(null);
+ }
+
+ /**
+ * Returns a node of the tree if it is present, otherwise returns null
+ *
+ * @param key
+ * key of node to find
+ */
+ public AbstractDataTreeNode findNodeAt(IPath key) {
+ AbstractDataTreeNode node = rootNode;
+ int segmentCount = key.segmentCount();
+ for (int i = 0; i < segmentCount; i++) {
+ node = node.childAtOrNull(key.segment(i));
+ if (node == null)
+ return null;
+ }
+ return node;
+ }
+
+ /**
+ * Returns a forward delta between the receiver and the given source tree,
+ * using the given comparer to compare data objects.
+ * The result describes the changes which, if assembled with the receiver,
+ * will produce the given source tree.
+ * In more detail, let c = a.forwardDeltaWith(b).
+ * c has the same content as b, but is represented as a delta tree with a as the parent.
+ * Also, c is immutable.
+ *
+ * There is no requirement that a and b be related, although it is usually more
+ * efficient if they are. The node keys are used as the basis of correlation
+ * between trees.
+ *
+ * Note that if b is already represented as a delta over a,
+ * then c will have the same internal structure as b.
+ * Thus the very common case of previous forwardDeltaWith: current
+ * is actually very fast when current is a modification of previous.
+ *
+ * @param sourceTree second delta tree to create a delta between
+ * @param comparer the comparer used to compare data objects
+ * @return the new delta
+ */
+ public DeltaDataTree forwardDeltaWith(DeltaDataTree sourceTree, IComparator comparer) {
+ DeltaDataTree newTree;
+ if (this == sourceTree) {
+ newTree = this.newEmptyDeltaTree();
+ } else if (sourceTree.hasAncestor(this)) {
+ AbstractDataTreeNode assembled = sourceTree.getRootNode();
+ DeltaDataTree treeParent = sourceTree;
+
+ /* Iterate through the sourceTree's ancestors until the receiver is reached */
+ while ((treeParent = treeParent.getParent()) != this) {
+ assembled = treeParent.getRootNode().assembleWith(assembled);
+ }
+ newTree = new DeltaDataTree(assembled, this);
+ newTree.simplify(comparer);
+ } else if (this.hasAncestor(sourceTree)) {
+ //create the delta backwards and then reverse it
+ newTree = sourceTree.forwardDeltaWith(this, comparer);
+ newTree = newTree.asBackwardDelta();
+ } else {
+ DataTreeNode thisCompleteRoot = (DataTreeNode) this.copyCompleteSubtree(rootKey());
+ DataTreeNode sourceTreeCompleteRoot = (DataTreeNode) sourceTree.copyCompleteSubtree(rootKey());
+ AbstractDataTreeNode deltaRoot = thisCompleteRoot.forwardDeltaWith(sourceTreeCompleteRoot, comparer);
+ newTree = new DeltaDataTree(deltaRoot, this);
+ }
+ newTree.immutable();
+ return newTree;
+ }
+
+ /**
+ * @see AbstractDataTree#getChildCount(IPath)
+ */
+ @Override
+ public int getChildCount(IPath parentKey) {
+ return getChildNodes(parentKey).length;
+ }
+
+ /**
+ * Returns the child nodes of a node in the tree.
+ */
+ protected AbstractDataTreeNode[] getChildNodes(IPath parentKey) {
+
+ /* Algorithm:
+ * for each delta in chain (going backwards),
+ * get list of child nodes, if any in delta
+ * assemble with previously seen list, if any
+ * break when complete tree found,
+ * report error if parent is missing or has been deleted
+ */
+
+ AbstractDataTreeNode[] childNodes = null;
+ int keyLength = parentKey.segmentCount();
+ for (DeltaDataTree tree = this; tree != null; tree = tree.parent) {
+ AbstractDataTreeNode node = tree.rootNode;
+ boolean complete = !node.isDelta();
+ for (int i = 0; i < keyLength; i++) {
+ node = node.childAtOrNull(parentKey.segment(i));
+ if (node == null) {
+ break;
+ }
+ if (!node.isDelta()) {
+ complete = true;
+ }
+ }
+ if (node != null) {
+ if (node.isDeleted()) {
+ break;
+ }
+ if (childNodes == null) {
+ childNodes = node.children;
+ } else {
+ // Be sure to assemble(old, new) rather than (new, old).
+ // Keep deleted nodes if we haven't encountered the complete node yet.
+ childNodes = AbstractDataTreeNode.assembleWith(node.children, childNodes, !complete);
+ }
+ }
+ if (complete) {
+ if (childNodes != null) {
+ return childNodes;
+ }
+ // Not found, but complete node encountered, so should not check parent tree.
+ break;
+ }
+ }
+ if (childNodes != null) {
+ // Some deltas carry info about children, but there is
+ // no complete node against which they describe deltas.
+ Assert.isTrue(false, Messages.dtree_malformedTree);
+ }
+
+ // Node is missing or has been deleted.
+ handleNotFound(parentKey);
+ return null;//should not get here
+ }
+
+ /**
+ * @see AbstractDataTree#getChildren(IPath)
+ */
+ @Override
+ public IPath[] getChildren(IPath parentKey) {
+ AbstractDataTreeNode[] childNodes = getChildNodes(parentKey);
+ int len = childNodes.length;
+ if (len == 0)
+ return NO_CHILDREN;
+ IPath[] answer = new IPath[len];
+ for (int i = 0; i < len; ++i)
+ answer[i] = parentKey.append(childNodes[i].name);
+ return answer;
+ }
+
+ /**
+ * Returns the data at a node of the tree.
+ *
+ * @param key
+ * key of node for which to return data.
+ */
+ @Override
+ public Object getData(IPath key) {
+
+ /* Algorithm:
+ * for each delta in chain (going backwards),
+ * get node, if any in delta
+ * if it carries data, return it
+ * break when complete tree found
+ * report error if node is missing or has been deleted
+ */
+
+ int keyLength = key.segmentCount();
+ for (DeltaDataTree tree = this; tree != null; tree = tree.parent) {
+ AbstractDataTreeNode node = tree.rootNode;
+ boolean complete = !node.isDelta();
+ for (int i = 0; i < keyLength; i++) {
+ node = node.childAtOrNull(key.segment(i));
+ if (node == null) {
+ break;
+ }
+ if (!node.isDelta()) {
+ complete = true;
+ }
+ }
+ if (node != null) {
+ if (node.hasData()) {
+ return node.getData();
+ } else if (node.isDeleted()) {
+ break;
+ }
+ }
+ if (complete) {
+ // Not found, but complete node encountered, so should not check parent tree.
+ break;
+ }
+ }
+ handleNotFound(key);
+ return null; //can't get here
+ }
+
+ /**
+ * @see AbstractDataTree#getNameOfChild(IPath, int)
+ */
+ @Override
+ public String getNameOfChild(IPath parentKey, int index) {
+ AbstractDataTreeNode[] childNodes = getChildNodes(parentKey);
+ return childNodes[index].name;
+ }
+
+ /**
+ * Returns the local names for the children of a node of the tree.
+ *
+ * @see AbstractDataTree#getNamesOfChildren(IPath)
+ */
+ @Override
+ public String[] getNamesOfChildren(IPath parentKey) {
+ AbstractDataTreeNode[] childNodes = getChildNodes(parentKey);
+ int len = childNodes.length;
+ String[] namesOfChildren = new String[len];
+ for (int i = 0; i < len; ++i)
+ namesOfChildren[i] = childNodes[i].name;
+ return namesOfChildren;
+ }
+
+ /**
+ * Returns the parent of the tree.
+ */
+ public DeltaDataTree getParent() {
+ return parent;
+ }
+
+ /**
+ * Returns the root node of the tree.
+ */
+ @Override
+ protected AbstractDataTreeNode getRootNode() {
+ return rootNode;
+ }
+
+ /**
+ * Returns true if the receiver's parent has the specified ancestor
+ *
+ * @param ancestor the ancestor in question
+ */
+ protected boolean hasAncestor(DeltaDataTree ancestor) {
+ DeltaDataTree myParent = this;
+ while ((myParent = myParent.getParent()) != null) {
+ if (myParent == ancestor) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if the receiver includes a node with
+ * the given key, false otherwise.
+ */
+ @Override
+ public boolean includes(IPath key) {
+ return searchNodeAt(key) != null;
+ }
+
+ public boolean isEmptyDelta() {
+ return rootNode.getChildren().length == 0;
+ }
+
+ /**
+ * Returns an object containing:
+ * - the node key
+ * - a flag indicating whether the specified node was found
+ * - the data for the node, if it was found
+ *
+ * @param key key of node for which we want to retrieve data.
+ */
+ @Override
+ public DataTreeLookup lookup(IPath key) {
+ int keyLength = key.segmentCount();
+ for (DeltaDataTree tree = this; tree != null; tree = tree.parent) {
+ AbstractDataTreeNode node = tree.rootNode;
+ boolean complete = !node.isDelta();
+ for (int i = 0; i < keyLength; i++) {
+ node = node.childAtOrNull(key.segment(i));
+ if (node == null) {
+ break;
+ }
+ complete |= !node.isDelta();
+ }
+ if (node != null) {
+ if (node.hasData()) {
+ return DataTreeLookup.newLookup(key, true, node.getData(), tree == this);
+ } else if (node.isDeleted()) {
+ break;
+ }
+ }
+ if (complete) {
+ // Not found, but complete node encountered, so should not check parent tree.
+ break;
+ }
+ }
+ return DataTreeLookup.newLookup(key, false, null);
+ }
+
+ /**
+ * Returns an object containing:
+ * - the node key
+ * - a flag indicating whether the specified node was found
+ * - the data for the node, if it was found
+ *
+ * This is a case-insensitive variant of the lookup
+ * method.
+ *
+ * @param key key of node for which we want to retrieve data.
+ */
+ public DataTreeLookup lookupIgnoreCase(IPath key) {
+ int keyLength = key.segmentCount();
+ for (DeltaDataTree tree = this; tree != null; tree = tree.parent) {
+ AbstractDataTreeNode node = tree.rootNode;
+ boolean complete = !node.isDelta();
+ for (int i = 0; i < keyLength; i++) {
+ node = node.childAtIgnoreCase(key.segment(i));
+ if (node == null) {
+ break;
+ }
+ complete |= !node.isDelta();
+ }
+ if (node != null) {
+ if (node.hasData()) {
+ return DataTreeLookup.newLookup(key, true, node.getData(), tree == this);
+ } else if (node.isDeleted()) {
+ break;
+ }
+ }
+ if (complete) {
+ // Not found, but complete node encountered, so should not check parent tree.
+ break;
+ }
+ }
+ return DataTreeLookup.newLookup(key, false, null);
+ }
+
+ /**
+ * Converts this tree's representation to be a complete tree, not a delta.
+ * This disconnects this tree from its parents.
+ * The parent trees are unaffected.
+ */
+ public void makeComplete() {
+ AbstractDataTreeNode assembled = getRootNode();
+ DeltaDataTree myParent = getParent();
+ while (myParent != null) {
+ assembled = myParent.getRootNode().assembleWith(assembled);
+ myParent = myParent.getParent();
+ }
+ setRootNode(assembled);
+ setParent(null);
+ }
+
+ /**
+ * Returns a complete node containing the contents of the subtree
+ * rooted at key in the receiver. Uses the public API.
+ *
+ * @param key
+ * key of subtree whose contents we want to copy.
+ */
+ protected AbstractDataTreeNode naiveCopyCompleteSubtree(IPath key) {
+ String[] childNames = getNamesOfChildren(key);
+ int numChildren = childNames.length;
+ AbstractDataTreeNode[] childNodes;
+ if (numChildren == 0) {
+ childNodes = AbstractDataTreeNode.NO_CHILDREN;
+ } else {
+ childNodes = new AbstractDataTreeNode[numChildren];
+ /* do for each child */
+ for (int i = numChildren; --i >= 0;) {
+ childNodes[i] = copyCompleteSubtree(key.append(childNames[i]));
+ }
+ }
+ return new DataTreeNode(key.lastSegment(), getData(key), childNodes);
+ }
+
+ /**
+ * Returns a new tree which represents an empty, mutable delta on the
+ * receiver. It is not possible to obtain a new delta tree if the receiver is
+ * not immutable, as subsequent changes to the receiver would affect the
+ * resulting delta.
+ */
+ public DeltaDataTree newEmptyDeltaTree() {
+ if (!isImmutable())
+ throw new IllegalArgumentException(Messages.dtree_notImmutable);
+ DeltaDataTree newTree = (DeltaDataTree) this.copy();
+ newTree.setParent(this);
+ newTree.emptyDelta();
+ return newTree;
+ }
+
+ /**
+ * Makes the receiver the root tree in the list of trees on which it is based.
+ * The receiver's representation becomes a complete tree, while its parents'
+ * representations become backward deltas based on the receiver.
+ * It is not possible to re-root a source tree that is not immutable, as this
+ * would require that its parents be expressed as deltas on a source tree
+ * which could still change.
+ *
+ * @exception RuntimeException
+ * receiver is not immutable
+ */
+ public DeltaDataTree reroot() {
+ /* self mutex critical region */
+ reroot(this);
+ return this;
+ }
+
+ /**
+ * Makes the given source tree the root tree in the list of trees on which it is based.
+ * The source tree's representation becomes a complete tree, while its parents'
+ * representations become backward deltas based on the source tree.
+ * It is not possible to re-root a source tree that is not immutable, as this
+ * would require that its parents be expressed as deltas on a source tree
+ * which could still change.
+ *
+ * @param sourceTree
+ * source tree to set as the new root
+ * @exception RuntimeException
+ * sourceTree is not immutable
+ */
+ protected void reroot(DeltaDataTree sourceTree) {
+ if (!sourceTree.isImmutable())
+ handleImmutableTree();
+ DeltaDataTree sourceParent = sourceTree.getParent();
+ if (sourceParent == null)
+ return;
+ this.reroot(sourceParent);
+ DeltaDataTree backwardDelta = sourceTree.asBackwardDelta();
+ DeltaDataTree complete = sourceParent.assembleWithForwardDelta(sourceTree);
+ sourceTree.setRootNode(complete.getRootNode());
+ sourceTree.setParent(null);
+ sourceParent.setRootNode(backwardDelta.getRootNode());
+ sourceParent.setParent(sourceTree);
+ }
+
+ /**
+ * Returns a complete node containing the contents of a subtree of the tree.
+ * Returns null if the node at this key does not exist. This is a thread-safe
+ * version of copyCompleteSubtree
+ *
+ * @param key key of subtree to copy
+ */
+ public AbstractDataTreeNode safeCopyCompleteSubtree(IPath key) {
+ AbstractDataTreeNode node = searchNodeAt(key);
+ if (node == null)
+ return null;
+ if (node.isDelta())
+ return safeNaiveCopyCompleteSubtree(key);
+ //copy the node in case the user wants to hammer the subtree name
+ return node.copy();
+ }
+
+ /**
+ * Returns a complete node containing the contents of the subtree
+ * rooted at @key in the receiver. Returns null if this node does not exist in
+ * the tree. This is a thread-safe version of naiveCopyCompleteSubtree
+ *
+ * @param key
+ * key of subtree whose contents we want to copy.
+ */
+ protected AbstractDataTreeNode safeNaiveCopyCompleteSubtree(IPath key) {
+ try {
+ String[] childNames = getNamesOfChildren(key);
+ int numChildren = childNames.length;
+ AbstractDataTreeNode[] childNodes;
+ if (numChildren == 0) {
+ childNodes = AbstractDataTreeNode.NO_CHILDREN;
+ } else {
+ childNodes = new AbstractDataTreeNode[numChildren];
+ /* do for each child */
+ int actualChildCount = 0;
+ for (int i = numChildren; --i >= 0;) {
+ childNodes[i] = safeCopyCompleteSubtree(key.append(childNames[i]));
+ if (childNodes[i] != null)
+ actualChildCount++;
+ }
+ //if there are less actual children due to concurrent deletion, shrink the child array
+ if (actualChildCount < numChildren) {
+ AbstractDataTreeNode[] actualChildNodes = new AbstractDataTreeNode[actualChildCount];
+ for (int iOld = 0, iNew = 0; iOld < numChildren; iOld++)
+ if (childNodes[iOld] != null)
+ actualChildNodes[iNew++] = childNodes[iOld];
+ childNodes = actualChildNodes;
+ }
+ }
+ return new DataTreeNode(key.lastSegment(), getData(key), childNodes);
+ } catch (ObjectNotFoundException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Returns the specified node. Search in the parent if necessary. Return null
+ * if the node is not found or if it has been deleted
+ */
+ protected AbstractDataTreeNode searchNodeAt(IPath key) {
+ int keyLength = key.segmentCount();
+ for (DeltaDataTree tree = this; tree != null; tree = tree.parent) {
+ AbstractDataTreeNode node = tree.rootNode;
+ boolean complete = !node.isDelta();
+ for (int i = 0; i < keyLength; i++) {
+ node = node.childAtOrNull(key.segment(i));
+ if (node == null) {
+ break;
+ }
+ if (!node.isDelta()) {
+ complete = true;
+ }
+ }
+ if (node != null) {
+ if (node.isDeleted())
+ break;
+ return node;
+ }
+ if (complete) {
+ // Not found, but complete node encountered, so should not check parent tree.
+ break;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * @see AbstractDataTree#setData(IPath, Object)
+ */
+ @Override
+ public void setData(IPath key, Object data) {
+ if (isImmutable())
+ handleImmutableTree();
+ if (!includes(key))
+ handleNotFound(key);
+ assembleNode(key, new DataDeltaNode(key.lastSegment(), data));
+ }
+
+ /**
+ * Sets the parent of the tree.
+ */
+ protected void setParent(DeltaDataTree aTree) {
+ parent = aTree;
+ }
+
+ /**
+ * Sets the root node of the tree
+ */
+ @Override
+ void setRootNode(AbstractDataTreeNode aNode) {
+ rootNode = aNode;
+ }
+
+ /**
+ * Simplifies the receiver:
+ * - replaces any DataDelta nodes with the same data as the parent
+ * with a NoDataDelta node
+ * - removes any empty (leaf NoDataDelta) nodes
+ */
+ protected void simplify(IComparator comparer) {
+ if (parent == null)
+ return;
+ setRootNode(rootNode.simplifyWithParent(rootKey(), parent, comparer));
+ }
+
+ /* (non-Javadoc)
+ * Method declared on IStringPoolParticipant
+ */
+ public void storeStrings(StringPool set){
+ AbstractDataTreeNode root = null;
+ for(DeltaDataTree dad = this ; dad != null; dad = dad.getParent()){
+ root = dad.getRootNode();
+ if (root != null)
+ root.storeStrings(set);
+ }
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/IComparator.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/IComparator.java
new file mode 100644
index 0000000000..05d7ce99a2
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/IComparator.java
@@ -0,0 +1,28 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2005 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.dtree;
+
+/**
+ * An interface for comparing two data tree objects. Provides information
+ * on how an object has changed from one tree to another.
+ */
+public interface IComparator {
+ /**
+ * Returns an integer describing the changes between two data objects
+ * in a data tree. The first three bits of the returned integer are
+ * used during calculation of delta trees. The remaining bits can be
+ * assigned any meaning that is useful to the client. If there is no
+ * change in the two data objects, this method must return 0.
+ *
+ * @see NodeComparison
+ */
+ int compare(Object o1, Object o2);
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/IDataFlattener.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/IDataFlattener.java
new file mode 100644
index 0000000000..3754eb3661
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/IDataFlattener.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2005 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.dtree;
+
+import java.io.*;
+import org.eclipse.core.runtime.IPath;
+
+/**
+ * The IElementInfoFlattener interface supports
+ * reading and writing element info objects.
+ */
+public interface IDataFlattener {
+ /**
+ * Reads a data object from the given input stream.
+ * @param path the path of the element to be read
+ * @param input the stream from which the element info should be read.
+ * @return the object associated with the given path,
+ * which may be null.
+ */
+ public Object readData(IPath path, DataInput input) throws IOException;
+
+ /**
+ * Writes the given data to the output stream.
+ *
N.B. The bytes written must be sufficient for the
+ * purposes of reading the object back in.
+ * @param path the element's path in the tree
+ * @param data the object associated with the given path,
+ * which may be null.
+ */
+ public void writeData(IPath path, Object data, DataOutput output) throws IOException;
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/NoDataDeltaNode.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/NoDataDeltaNode.java
new file mode 100644
index 0000000000..900353150a
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/NoDataDeltaNode.java
@@ -0,0 +1,133 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2014 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.dtree;
+
+import org.eclipse.core.runtime.IPath;
+
+/**
+ * A NoDataDeltaNodeis a node in a delta tree whose subtree contains
+ * differences since the delta's parent. Refer to the DeltaDataTree
+ * API and class comment for details.
+ *
+ * @see DeltaDataTree
+ */
+public class NoDataDeltaNode extends AbstractDataTreeNode {
+ /**
+ * Creates a new empty delta.
+ */
+ public NoDataDeltaNode(String name) {
+ this(name, NO_CHILDREN);
+ }
+
+ /**
+ * Creates a new data tree node
+ *
+ * @param name name of new node
+ * @param children children of the new node
+ */
+ public NoDataDeltaNode(String name, AbstractDataTreeNode[] children) {
+ super(name, children);
+ }
+
+ /**
+ * Creates a new data tree node
+ *
+ * @param localName name of new node
+ * @param childNode single child for new node
+ */
+ NoDataDeltaNode(String localName, AbstractDataTreeNode childNode) {
+ super(localName, new AbstractDataTreeNode[] {childNode});
+ }
+
+ /**
+ * @see AbstractDataTreeNode#asBackwardDelta(DeltaDataTree, DeltaDataTree, IPath)
+ */
+ @Override
+ AbstractDataTreeNode asBackwardDelta(DeltaDataTree myTree, DeltaDataTree parentTree, IPath key) {
+ int numChildren = children.length;
+ if (numChildren == 0)
+ return new NoDataDeltaNode(name, NO_CHILDREN);
+ AbstractDataTreeNode[] newChildren = new AbstractDataTreeNode[numChildren];
+ for (int i = numChildren; --i >= 0;) {
+ newChildren[i] = children[i].asBackwardDelta(myTree, parentTree, key.append(children[i].getName()));
+ }
+ return new NoDataDeltaNode(name, newChildren);
+ }
+
+ /**
+ * @see AbstractDataTreeNode#compareWithParent(IPath, DeltaDataTree, IComparator)
+ */
+ @Override
+ AbstractDataTreeNode compareWithParent(IPath key, DeltaDataTree parent, IComparator comparator) {
+ AbstractDataTreeNode[] comparedChildren = compareWithParent(children, key, parent, comparator);
+ Object oldData = parent.getData(key);
+ return new DataTreeNode(key.lastSegment(), new NodeComparison(oldData, oldData, NodeComparison.K_CHANGED, 0), comparedChildren);
+ }
+
+ /**
+ * Creates and returns a new copy of the receiver. Makes a deep copy of
+ * children, but a shallow copy of name and data.
+ */
+ @Override
+ AbstractDataTreeNode copy() {
+ AbstractDataTreeNode[] childrenCopy;
+ if (children.length == 0) {
+ childrenCopy = NO_CHILDREN;
+ } else {
+ childrenCopy = new AbstractDataTreeNode[children.length];
+ System.arraycopy(children, 0, childrenCopy, 0, children.length);
+ }
+ return new NoDataDeltaNode(name, childrenCopy);
+ }
+
+ /**
+ * Returns true if the receiver represents delta information,
+ * false if it represents the complete information.
+ */
+ @Override
+ boolean isDelta() {
+ return true;
+ }
+
+ /**
+ * Returns true if the receiver is an empty delta node, false otherwise.
+ */
+ @Override
+ boolean isEmptyDelta() {
+ return this.size() == 0;
+ }
+
+ /**
+ * Simplifies the given node, and returns its replacement.
+ */
+ @Override
+ AbstractDataTreeNode simplifyWithParent(IPath key, DeltaDataTree parent, IComparator comparer) {
+ AbstractDataTreeNode[] simplifiedChildren = simplifyWithParent(children, key, parent, comparer);
+ return new NoDataDeltaNode(name, simplifiedChildren);
+ }
+
+ /**
+ * Returns a unicode representation of the node. This method is used
+ * for debugging purposes only (no NLS support needed)
+ */
+ @Override
+ public String toString() {
+ return "a NoDataDeltaNode(" + this.getName() + ") with " + getChildren().length + " children."; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+
+ /**
+ * Return a constant describing the type of node.
+ */
+ @Override
+ int type() {
+ return T_NO_DATA_DELTA_NODE;
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/NodeComparison.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/NodeComparison.java
new file mode 100644
index 0000000000..a71a2f7dc6
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/NodeComparison.java
@@ -0,0 +1,136 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2014 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.dtree;
+
+/**
+ * This class represents the changes in a single node between two data trees.
+ */
+public final class NodeComparison {
+ /**
+ * The data of the old tree
+ */
+ private Object oldData;
+
+ /**
+ * The data of the new tree
+ */
+ private Object newData;
+
+ /**
+ * Integer describing changes between two data elements
+ */
+ private int comparison;
+
+ /**
+ * Extra integer that can be assigned by the client
+ */
+ private int userInt;
+
+ /**
+ * Special bits in the comparison flag to indicate the type of change
+ */
+ public final static int K_ADDED = 1;
+ public final static int K_REMOVED = 2;
+ public final static int K_CHANGED = 4;
+
+ NodeComparison(Object oldData, Object newData, int realComparison, int userComparison) {
+ this.oldData = oldData;
+ this.newData = newData;
+ this.comparison = realComparison;
+ this.userInt = userComparison;
+ }
+
+ /**
+ * Reverse the nature of the comparison.
+ */
+ NodeComparison asReverseComparison(IComparator comparator) {
+ /* switch the data */
+ Object tempData = oldData;
+ oldData = newData;
+ newData = tempData;
+
+ /* re-calculate user comparison */
+ userInt = comparator.compare(oldData, newData);
+
+ if (comparison == K_ADDED) {
+ comparison = K_REMOVED;
+ } else {
+ if (comparison == K_REMOVED) {
+ comparison = K_ADDED;
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Returns an integer describing the changes between the two data objects.
+ * The four possible values are K_ADDED, K_REMOVED, K_CHANGED, or 0 representing
+ * no change.
+ */
+ public int getComparison() {
+ return comparison;
+ }
+
+ /**
+ * Returns the data of the new node.
+ */
+ public Object getNewData() {
+ return newData;
+ }
+
+ /**
+ * Returns the data of the old node.
+ */
+ public Object getOldData() {
+ return oldData;
+ }
+
+ /**
+ * Returns the client specified integer
+ */
+ public int getUserComparison() {
+ return userInt;
+ }
+
+ /**
+ * Returns true if this comparison has no change, and false otherwise.
+ */
+ boolean isUnchanged() {
+ return userInt == 0;
+ }
+
+ /**
+ * For debugging
+ */
+ @Override
+ public String toString() {
+ StringBuffer buf = new StringBuffer("NodeComparison("); //$NON-NLS-1$
+ switch (comparison) {
+ case K_ADDED :
+ buf.append("Added, "); //$NON-NLS-1$
+ break;
+ case K_REMOVED :
+ buf.append("Removed, "); //$NON-NLS-1$
+ break;
+ case K_CHANGED :
+ buf.append("Changed, "); //$NON-NLS-1$
+ break;
+ case 0 :
+ buf.append("No change, "); //$NON-NLS-1$
+ break;
+ default :
+ buf.append("Corrupt(" + comparison + "), "); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ buf.append(userInt);
+ buf.append(")"); //$NON-NLS-1$
+ return buf.toString();
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/ObjectNotFoundException.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/ObjectNotFoundException.java
new file mode 100644
index 0000000000..f2850b4b62
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/ObjectNotFoundException.java
@@ -0,0 +1,30 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2005 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.dtree;
+
+/**
+ * This exception is thrown when an attempt is made to reference a source tree
+ * element that does not exist in the given tree.
+ */
+public class ObjectNotFoundException extends RuntimeException {
+ /**
+ * All serializable objects should have a stable serialVersionUID
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * ObjectNotFoundException constructor comment.
+ * @param s java.lang.String
+ */
+ public ObjectNotFoundException(String s) {
+ super(s);
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/TestHelper.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/TestHelper.java
new file mode 100644
index 0000000000..96657f97f2
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/TestHelper.java
@@ -0,0 +1,23 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2005 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.dtree;
+
+/**
+ * Helper class for the test suite.
+ */
+public class TestHelper {
+ /**
+ * Returns the root node of a tree.
+ */
+ public static AbstractDataTreeNode getRootNode(AbstractDataTree tree) {
+ return tree.getRootNode();
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/AutoBuildJob.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/AutoBuildJob.java
new file mode 100644
index 0000000000..e7f95b98c9
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/AutoBuildJob.java
@@ -0,0 +1,276 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2015 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM - Initial API and implementation
+ * Warren Paul (Nokia) - Fix for build scheduling bug 209236
+ *******************************************************************************/
+package org.eclipse.core.internal.events;
+
+import org.eclipse.core.internal.resources.*;
+import org.eclipse.core.internal.utils.Messages;
+import org.eclipse.core.internal.utils.Policy;
+import org.eclipse.core.resources.*;
+import org.eclipse.core.runtime.*;
+import org.eclipse.core.runtime.Preferences.PropertyChangeEvent;
+import org.eclipse.core.runtime.jobs.ISchedulingRule;
+import org.eclipse.core.runtime.jobs.Job;
+import org.osgi.framework.Bundle;
+
+/**
+ * The job for performing workspace auto-builds, and pre- and post- autobuild
+ * notification. This job is run whenever the workspace changes regardless
+ * of whether autobuild is on or off.
+ */
+class AutoBuildJob extends Job implements Preferences.IPropertyChangeListener {
+ private boolean avoidBuild = false;
+ private boolean buildNeeded = false;
+ private boolean forceBuild = false;
+ /**
+ * Indicates that another thread tried to modify the workspace during
+ * the autobuild. The autobuild should be immediately rescheduled
+ * so that it will run as soon as the next workspace modification completes.
+ */
+ private boolean interrupted = false;
+ private boolean isAutoBuilding = false;
+ private long lastBuild = 0L;
+ private Preferences preferences = ResourcesPlugin.getPlugin().getPluginPreferences();
+ private final Bundle systemBundle = Platform.getBundle("org.eclipse.osgi"); //$NON-NLS-1$
+ private Workspace workspace;
+
+ AutoBuildJob(Workspace workspace) {
+ super(Messages.events_building_0);
+ setRule(workspace.getRoot());
+ setPriority(BUILD);
+ isAutoBuilding = workspace.isAutoBuilding();
+ this.workspace = workspace;
+ this.preferences.addPropertyChangeListener(this);
+ }
+
+ /**
+ * Used to prevent auto-builds at the end of operations that contain
+ * explicit builds
+ */
+ synchronized void avoidBuild() {
+ avoidBuild = true;
+ }
+
+ @Override
+ public boolean belongsTo(Object family) {
+ return family == ResourcesPlugin.FAMILY_AUTO_BUILD;
+ }
+
+ /**
+ * Instructs the build job that a build is required. Ensure the build
+ * job is scheduled to run.
+ * @param needsBuild Whether a build is required, either due to
+ * workspace change or other factor that invalidates the built state.
+ */
+ synchronized void build(boolean needsBuild) {
+ buildNeeded |= needsBuild;
+ long delay = computeScheduleDelay();
+ int state = getState();
+ if (Policy.DEBUG_BUILD_NEEDED)
+ Policy.debug("Auto-Build requested, needsBuild: " + needsBuild + " state: " + state + " delay: " + delay); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ if (needsBuild && Policy.DEBUG_BUILD_NEEDED_STACK && state != Job.RUNNING)
+ Policy.debug(new RuntimeException("Build needed")); //$NON-NLS-1$
+ //don't mess with the interrupt flag if the job is still running
+ if (state != Job.RUNNING)
+ setInterrupted(false);
+ switch (state) {
+ case Job.SLEEPING :
+ wakeUp(delay);
+ break;
+ case NONE :
+ try {
+ setSystem(!isAutoBuilding);
+ } catch (IllegalStateException e) {
+ //ignore - the job has been scheduled since we last checked its state
+ }
+ schedule(delay);
+ break;
+ }
+ }
+
+ /**
+ * Computes the delay time that autobuild should be scheduled with. The
+ * value will be in the range (MIN_BUILD_DELAY, MAX_BUILD_DELAY).
+ */
+ private long computeScheduleDelay() {
+ // don't assume that the last build time is always less than the current system time
+ long maxDelay = Math.min(Policy.MAX_BUILD_DELAY, Policy.MAX_BUILD_DELAY + lastBuild - System.currentTimeMillis());
+ return Math.max(Policy.MIN_BUILD_DELAY, maxDelay);
+ }
+
+ /**
+ * The autobuild job has been canceled. There are two flavours of
+ * cancel, explicit user cancelation, and implicit interruption due to another
+ * thread trying to modify the workspace. In the latter case, we must
+ * make sure the build is immediately rescheduled if it was interrupted
+ * by another thread, so that clients waiting to join autobuild will properly
+ * continue waiting
+ * @return a status with severity CANCEL
+ */
+ private synchronized IStatus canceled() {
+ //regardless of the form of cancelation, the build state is not happy
+ buildNeeded = true;
+ //schedule a rebuild immediately if build was implicitly canceled
+ if (interrupted) {
+ if (Policy.DEBUG_BUILD_INTERRUPT)
+ Policy.debug("Scheduling rebuild due to interruption"); //$NON-NLS-1$
+ setInterrupted(false);
+ schedule(computeScheduleDelay());
+ }
+ return Status.CANCEL_STATUS;
+ }
+
+ private void doBuild(IProgressMonitor monitor) throws CoreException, OperationCanceledException {
+ monitor = Policy.monitorFor(monitor);
+ try {
+ monitor.beginTask("", Policy.opWork); //$NON-NLS-1$
+ final ISchedulingRule rule = workspace.getRuleFactory().buildRule();
+ try {
+ workspace.prepareOperation(rule, monitor);
+ workspace.beginOperation(true);
+ final int trigger = IncrementalProjectBuilder.AUTO_BUILD;
+ workspace.broadcastBuildEvent(workspace, IResourceChangeEvent.PRE_BUILD, trigger);
+ IStatus result = Status.OK_STATUS;
+ try {
+ if (shouldBuild())
+ result = workspace.getBuildManager().build(workspace.getBuildOrder(), ICoreConstants.EMPTY_BUILD_CONFIG_ARRAY, trigger, Policy.subMonitorFor(monitor, Policy.opWork));
+ } finally {
+ //always send POST_BUILD if there has been a PRE_BUILD
+ workspace.broadcastBuildEvent(workspace, IResourceChangeEvent.POST_BUILD, trigger);
+ }
+ if (!result.isOK())
+ throw new ResourceException(result);
+ buildNeeded = false;
+ } finally {
+ //building may close the tree, but we are still inside an
+ // operation so open it
+ if (workspace.getElementTree().isImmutable())
+ workspace.newWorkingTree();
+ workspace.endOperation(rule, false, Policy.subMonitorFor(monitor, Policy.endOpWork));
+ }
+ } finally {
+ monitor.done();
+ }
+ }
+
+ /**
+ * Forces an autobuild to occur, even if nothing has changed since the last
+ * build. This is used to force a build after a clean.
+ */
+ public void forceBuild() {
+ forceBuild = true;
+ }
+
+ /**
+ * Another thread is attempting to modify the workspace. Flag the auto-build
+ * as interrupted so that it will cancel and reschedule itself
+ */
+ synchronized void interrupt() {
+ //if already interrupted, do nothing
+ if (interrupted)
+ return;
+ switch (getState()) {
+ case NONE :
+ return;
+ case WAITING :
+ //put the job to sleep if it is waiting to run
+ setInterrupted(!sleep());
+ break;
+ case RUNNING :
+ //make sure autobuild doesn't interrupt itself
+ if (Job.getJobManager().currentJob() == this)
+ return;
+ setInterrupted(true);
+ break;
+ }
+ //clear the autobuild avoidance flag if we were interrupted
+ if (interrupted)
+ avoidBuild = false;
+ }
+
+ synchronized boolean isInterrupted() {
+ if (interrupted)
+ return true;
+ //check if another job is blocked by the build job
+ if (isBlocking())
+ setInterrupted(true);
+ return interrupted;
+ }
+
+ @Deprecated
+ @Override
+ public void propertyChange(PropertyChangeEvent event) {
+ if (!event.getProperty().equals(ResourcesPlugin.PREF_AUTO_BUILDING))
+ return;
+ // get the new value of auto-build directly from the preferences
+ boolean wasAutoBuilding = isAutoBuilding;
+ isAutoBuilding = preferences.getBoolean(ResourcesPlugin.PREF_AUTO_BUILDING);
+ //force a build if autobuild has been turned on
+ if (!forceBuild && !wasAutoBuilding && isAutoBuilding) {
+ forceBuild = true;
+ build(false);
+ }
+ }
+
+ @Override
+ public IStatus run(IProgressMonitor monitor) {
+ //synchronized in case build starts during checkCancel
+ synchronized (this) {
+ if (monitor.isCanceled()) {
+ return canceled();
+ }
+ }
+ //if the system is shutting down, don't build
+ if (systemBundle.getState() == Bundle.STOPPING)
+ return Status.OK_STATUS;
+ try {
+ doBuild(monitor);
+ lastBuild = System.currentTimeMillis();
+ //if the build was successful then it should not be recorded as interrupted
+ setInterrupted(false);
+ return Status.OK_STATUS;
+ } catch (OperationCanceledException e) {
+ return canceled();
+ } catch (CoreException sig) {
+ return sig.getStatus();
+ }
+ }
+
+ /**
+ * Sets or clears the interrupted flag.
+ */
+ private synchronized void setInterrupted(boolean value) {
+ interrupted = value;
+ if (interrupted && Policy.DEBUG_BUILD_INTERRUPT)
+ Policy.debug(new RuntimeException("Autobuild was interrupted")); //$NON-NLS-1$
+ }
+
+ /**
+ * Returns true if a build is actually needed, and false otherwise.
+ */
+ private synchronized boolean shouldBuild() {
+ try {
+ //if auto-build is off then we never run
+ if (!workspace.isAutoBuilding())
+ return false;
+ //build if the workspace requires a build (description changes)
+ if (forceBuild)
+ return true;
+ if (avoidBuild)
+ return false;
+ //return whether there have been any changes to the workspace tree.
+ return buildNeeded;
+ } finally {
+ //regardless of the result, clear the build flags for next time
+ forceBuild = avoidBuild = buildNeeded = false;
+ }
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuildCommand.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuildCommand.java
new file mode 100644
index 0000000000..4b0ae9f4a1
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuildCommand.java
@@ -0,0 +1,314 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2015 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * James Blackburn (Broadcom Corp.) - Custom trigger builder #equals
+ * Broadcom Corporation - ongoing development
+ *******************************************************************************/
+package org.eclipse.core.internal.events;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.eclipse.core.internal.resources.ModelObject;
+import org.eclipse.core.resources.*;
+import org.eclipse.core.runtime.*;
+
+/**
+ * The concrete implementation of ICommand. This object
+ * stores information about a particular type of builder.
+ *
+ * If the builder has been instantiated, a reference to the builder is held.
+ * If the builder supports multiple build configurations, a reference to the
+ * builder for each configuration is held.
+ */
+public class BuildCommand extends ModelObject implements ICommand {
+ /**
+ * Internal flag masks for different build triggers.
+ */
+ private static final int MASK_AUTO = 0x01;
+ private static final int MASK_INCREMENTAL = 0x02;
+ private static final int MASK_FULL = 0x04;
+ private static final int MASK_CLEAN = 0x08;
+
+ /**
+ * Flag bit indicating if this build command is configurable
+ */
+ private static final int MASK_CONFIGURABLE = 0x10;
+
+ /**
+ * Flag bit indicating if the configurable bit has been loaded from
+ * the builder extension declaration in XML yet.
+ */
+ private static final int MASK_CONFIG_COMPUTED = 0x20;
+
+ private static final int ALL_TRIGGERS = MASK_AUTO | MASK_CLEAN | MASK_FULL | MASK_INCREMENTAL;
+
+ protected HashMap arguments = new HashMap(0);
+
+ /** Have we checked the supports configurations flag */
+ private boolean supportsConfigurationsCalculated;
+ /** Does this builder support configurations */
+ private boolean supportsConfigurations;
+ /**
+ * The builder instance for this command. Null if the builder has
+ * not yet been instantiated.
+ */
+ private IncrementalProjectBuilder builder;
+ /**
+ * The builders for this command if the builder supports multiple configurations
+ */
+ private HashMap builders;
+
+ /**
+ * The triggers that this builder will respond to. Since build triggers are not
+ * bit-maskable, we use internal bit masks to represent each
+ * trigger (MASK_* constants). By default, a command responds to all
+ * build triggers.
+ */
+ private int triggers = ALL_TRIGGERS;
+
+ /**
+ * Returns the trigger bit mask for the given trigger constant.
+ */
+ private static int maskForTrigger(int trigger) {
+ switch (trigger) {
+ case IncrementalProjectBuilder.AUTO_BUILD :
+ return MASK_AUTO;
+ case IncrementalProjectBuilder.INCREMENTAL_BUILD :
+ return MASK_INCREMENTAL;
+ case IncrementalProjectBuilder.FULL_BUILD :
+ return MASK_FULL;
+ case IncrementalProjectBuilder.CLEAN_BUILD :
+ return MASK_CLEAN;
+ }
+ return 0;
+ }
+
+ public BuildCommand() {
+ super(""); //$NON-NLS-1$
+ }
+
+ @Override
+ public Object clone() {
+ BuildCommand result = null;
+ result = (BuildCommand) super.clone();
+ if (result == null)
+ return null;
+ result.setArguments(getArguments());
+ //don't let references to builder instances leak out because they reference trees
+ result.setBuilders(null);
+ return result;
+ }
+
+ /**
+ * Computes whether this build command allows configuration of its
+ * triggers, based on information in the builder extension declaration.
+ */
+ private void computeIsConfigurable() {
+ triggers |= MASK_CONFIG_COMPUTED;
+ IExtension extension = Platform.getExtensionRegistry().getExtension(ResourcesPlugin.PI_RESOURCES, ResourcesPlugin.PT_BUILDERS, name);
+ if (extension != null) {
+ IConfigurationElement[] configs = extension.getConfigurationElements();
+ if (configs.length != 0) {
+ String value = configs[0].getAttribute("isConfigurable"); //$NON-NLS-1$
+ setConfigurable(value != null && value.equalsIgnoreCase(Boolean.TRUE.toString()));
+ }
+ }
+ }
+
+ /* (non-Javadoc)
+ * Method declared on Object
+ */
+ @Override
+ public boolean equals(Object object) {
+ if (this == object)
+ return true;
+ if (!(object instanceof BuildCommand))
+ return false;
+ BuildCommand command = (BuildCommand) object;
+ // equal if same builder name, arguments, and triggers
+ return getBuilderName().equals(command.getBuilderName()) && getArguments(false).equals(command.getArguments(false)) && (triggers & ALL_TRIGGERS) == (command.triggers & ALL_TRIGGERS);
+ }
+
+ /**
+ * @see ICommand#getArguments()
+ */
+ @Override
+ public Map getArguments() {
+ return getArguments(true);
+ }
+
+ @SuppressWarnings({"unchecked"})
+ public Map getArguments(boolean makeCopy) {
+ return arguments == null ? null : (makeCopy ? (Map) arguments.clone() : arguments);
+ }
+
+ /**
+ * @return Map {@link IBuildConfiguration} -> {@link IncrementalProjectBuilder} if
+ * this build command supports multiple configurations. Otherwise return the {@link IncrementalProjectBuilder}
+ * associated with this build command.
+ */
+ public Object getBuilders() {
+ if (supportsConfigs())
+ return builders;
+ return builder;
+ }
+
+ /**
+ * Return the {@link IncrementalProjectBuilder} for the {@link IBuildConfiguration}
+ * If this builder is configuration agnostic, the same {@link IncrementalProjectBuilder} is
+ * returned for all configurations.
+ * @param config
+ * @return {@link IncrementalProjectBuilder} corresponding to config
+ */
+ public IncrementalProjectBuilder getBuilder(IBuildConfiguration config) {
+ if (builders != null && supportsConfigs())
+ return builders.get(config);
+ return builder;
+ }
+
+ /**
+ * @see ICommand#getBuilderName()
+ */
+ @Override
+ public String getBuilderName() {
+ return getName();
+ }
+
+ /* (non-Javadoc)
+ * Method declared on Object
+ */
+ @Override
+ public int hashCode() {
+ // hash on name and trigger
+ return 37 * getName().hashCode() + (ALL_TRIGGERS & triggers);
+ }
+
+ /**
+ * @see ICommand#isBuilding(int)
+ */
+ @Override
+ public boolean isBuilding(int trigger) {
+ return (triggers & maskForTrigger(trigger)) != 0;
+ }
+
+ @Override
+ public boolean isConfigurable() {
+ if ((triggers & MASK_CONFIG_COMPUTED) == 0)
+ computeIsConfigurable();
+ return (triggers & MASK_CONFIGURABLE) != 0;
+ }
+
+ public boolean supportsConfigs() {
+ if (!supportsConfigurationsCalculated) {
+ IExtension extension = Platform.getExtensionRegistry().getExtension(ResourcesPlugin.PI_RESOURCES, ResourcesPlugin.PT_BUILDERS, name);
+ if (extension != null) {
+ IConfigurationElement[] configs = extension.getConfigurationElements();
+ if (configs.length != 0) {
+ String value = configs[0].getAttribute("supportsConfigurations"); //$NON-NLS-1$
+ supportsConfigurations = (value != null && value.equalsIgnoreCase(Boolean.TRUE.toString()));
+ }
+ }
+ supportsConfigurationsCalculated = true;
+ }
+ return supportsConfigurations;
+ }
+
+ /**
+ * @see ICommand#setArguments(Map)
+ */
+ @Override
+ public void setArguments(Map value) {
+ // copy parameter for safety's sake
+ arguments = value == null ? null : new HashMap(value);
+ }
+
+ /**
+ * Set the IncrementalProjectBuilder(s) for this command
+ * @param value
+ */
+ @SuppressWarnings("unchecked")
+ public void setBuilders(Object value) {
+ if (value == null) {
+ builder = null;
+ builders = null;
+ } else {
+ if (value instanceof IncrementalProjectBuilder)
+ builder = (IncrementalProjectBuilder) value;
+ else
+ builders = new HashMap((Map) value);
+ }
+ }
+
+ /**
+ * Add an IncrementalProjectBuilder for the given configuration.
+ * For builders which don't respond to multiple configurations, there's only one builder
+ * instance.
+ * @param config
+ * @param newBuilder
+ */
+ public void addBuilder(IBuildConfiguration config, IncrementalProjectBuilder newBuilder) {
+ // Builder shouldn't already exist in this build command
+ IncrementalProjectBuilder currentBuilder = builders == null ? null : builders.get(config);
+ if (currentBuilder != null)
+ Assert.isTrue(false, "Current builder: " + currentBuilder.getClass().getName() + ", new builder: " + newBuilder.getClass().getName() + ", configuration: " + config); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ if (builder != null)
+ Assert.isTrue(false, "Current builder: " + builder.getClass().getName() + ", new builder: " + newBuilder.getClass().getName() + ", configuration: " + config); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+
+ if (supportsConfigs()) {
+ if (builders == null)
+ builders = new HashMap(1);
+ builders.put(config, newBuilder);
+ } else
+ builder = newBuilder;
+ }
+
+ /**
+ * @see ICommand#setBuilderName(String)
+ */
+ @Override
+ public void setBuilderName(String value) {
+ //don't allow builder name to be null
+ setName(value == null ? "" : value); //$NON-NLS-1$
+ }
+
+ /**
+ * @see ICommand#setBuilding(int, boolean)
+ */
+ @Override
+ public void setBuilding(int trigger, boolean value) {
+ if (!isConfigurable())
+ return;
+ if (value)
+ triggers |= maskForTrigger(trigger);
+ else
+ triggers &= ~maskForTrigger(trigger);
+ }
+
+ /**
+ * Sets whether this build command allows its build triggers to be configured.
+ * This value should only be set when the builder extension declaration is
+ * read from the registry, or when a build command is read from the project
+ * description file on disk. The value is not otherwise mutable.
+ */
+ public void setConfigurable(boolean value) {
+ triggers |= MASK_CONFIG_COMPUTED;
+ if (value)
+ triggers |= MASK_CONFIGURABLE;
+ else
+ triggers = ALL_TRIGGERS;
+ }
+
+ /**
+ * For debugging purposes only
+ */
+ @Override
+ public String toString() {
+ return "BuildCommand(" + getName() + ")";//$NON-NLS-1$ //$NON-NLS-2$
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuildContext.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuildContext.java
new file mode 100644
index 0000000000..ac750da936
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuildContext.java
@@ -0,0 +1,128 @@
+/*******************************************************************************
+ * Copyright (c) 2010, 2015 Broadcom Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Broadcom Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.events;
+
+import java.util.Arrays;
+import org.eclipse.core.resources.IBuildConfiguration;
+import org.eclipse.core.resources.IBuildContext;
+import org.eclipse.core.runtime.Assert;
+
+/**
+ * Concrete implementation of a build context
+ */
+public class BuildContext implements IBuildContext {
+
+ /** The Build Configuration currently being built */
+ private final IBuildConfiguration buildConfiguration;
+ /** Configurations user requested to be built */
+ private final IBuildConfiguration[] requestedBuilt;
+ /** The configurations built as part of this build invocations*/
+ private final IBuildConfiguration[] buildOrder;
+
+ /**
+ * Create an empty build context for the given project configuration.
+ * @param buildConfiguration the project configuration being built, that we need the context for
+ */
+ public BuildContext(IBuildConfiguration buildConfiguration) {
+ this.buildConfiguration = buildConfiguration;
+ requestedBuilt = buildOrder = new IBuildConfiguration[] {buildConfiguration};
+ }
+
+ /**
+ * Create a build context for the given project configuration.
+ * @param buildConfiguration the project configuration being built, that we need the context for
+ * @param requestedBuilt an array of configurations the user actually requested to be built
+ * @param buildOrder the build order for the entire build, indicating how cycles etc. have been resolved
+ */
+ public BuildContext(IBuildConfiguration buildConfiguration, IBuildConfiguration[] requestedBuilt, IBuildConfiguration[] buildOrder) {
+ this.buildConfiguration = buildConfiguration;
+ this.requestedBuilt = requestedBuilt;
+ this.buildOrder = buildOrder;
+ }
+
+ private int findBuildConfigurationIndex() {
+ int position = -1;
+ for (int i = 0; i < buildOrder.length; i++) {
+ if (buildOrder[i].equals(buildConfiguration)) {
+ position = i;
+ break;
+ }
+ }
+ Assert.isTrue(0 <= position && position < buildOrder.length);
+ return position;
+ }
+
+ @Override
+ public IBuildConfiguration[] getRequestedConfigs() {
+ return requestedBuilt.clone();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.core.resources.IBuildContext#getAllReferencedBuildConfigurations()
+ */
+ @Override
+ public IBuildConfiguration[] getAllReferencedBuildConfigs() {
+ int position = findBuildConfigurationIndex();
+ IBuildConfiguration[] builtBefore = new IBuildConfiguration[position];
+ System.arraycopy(buildOrder, 0, builtBefore, 0, builtBefore.length);
+ return builtBefore;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.core.resources.IBuildContext#getAllReferencingBuildConfigurations()
+ */
+ @Override
+ public IBuildConfiguration[] getAllReferencingBuildConfigs() {
+ int position = findBuildConfigurationIndex();
+ IBuildConfiguration[] builtAfter = new IBuildConfiguration[buildOrder.length - position - 1];
+ System.arraycopy(buildOrder, position + 1, builtAfter, 0, builtAfter.length);
+ return builtAfter;
+ }
+
+ private static final int hashCode(IBuildConfiguration[] array) {
+ final int prime = 31;
+ int result = 1;
+ for (int i = 0; i < array.length; i++)
+ result = prime * result + array[i].hashCode();
+ return result;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + buildConfiguration.hashCode();
+ result = prime * result + hashCode(requestedBuilt);
+ result = prime * result + hashCode(buildOrder);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ BuildContext other = (BuildContext) obj;
+ if (!buildConfiguration.equals(other.buildConfiguration))
+ return false;
+ if (!Arrays.equals(requestedBuilt, other.requestedBuilt))
+ return false;
+ if (!Arrays.equals(buildOrder, other.buildOrder))
+ return false;
+ return true;
+ }
+
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuildManager.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuildManager.java
new file mode 100644
index 0000000000..10e2d0678e
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuildManager.java
@@ -0,0 +1,1157 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2015 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * Isaac Pacht (isaacp3@gmail.com) - fix for bug 206540
+ * Anton Leherbauer (Wind River) - [305858] Allow Builder to return null rule
+ * James Blackburn (Broadcom) - [306822] Provide Context for Builder getRule()
+ * Broadcom Corporation - ongoing development
+ *******************************************************************************/
+package org.eclipse.core.internal.events;
+
+import java.util.*;
+import org.eclipse.core.internal.dtree.DeltaDataTree;
+import org.eclipse.core.internal.resources.*;
+import org.eclipse.core.internal.utils.Messages;
+import org.eclipse.core.internal.utils.Policy;
+import org.eclipse.core.internal.watson.ElementTree;
+import org.eclipse.core.resources.*;
+import org.eclipse.core.runtime.*;
+import org.eclipse.core.runtime.jobs.*;
+import org.eclipse.osgi.util.NLS;
+import org.osgi.framework.Bundle;
+
+public class BuildManager implements ICoreConstants, IManager, ILifecycleListener {
+
+ /**
+ * Cache used to optimize the common case of an autobuild against
+ * a workspace where only a single project has changed (and hence
+ * only a single delta is interesting).
+ */
+ class DeltaCache {
+ private Object delta;
+ private ElementTree newTree;
+ private ElementTree oldTree;
+ private IPath projectPath;
+
+ public void cache(IPath project, ElementTree anOldTree, ElementTree aNewTree, Object aDelta) {
+ this.projectPath = project;
+ this.oldTree = anOldTree;
+ this.newTree = aNewTree;
+ this.delta = aDelta;
+ }
+
+ public void flush() {
+ this.projectPath = null;
+ this.oldTree = null;
+ this.newTree = null;
+ this.delta = null;
+ }
+
+ /**
+ * Returns the cached resource delta for the given project and trees, or
+ * null if there is no matching delta in the cache.
+ */
+ public Object getDelta(IPath project, ElementTree anOldTree, ElementTree aNewTree) {
+ if (delta == null)
+ return null;
+ boolean pathsEqual = projectPath == null ? project == null : projectPath.equals(project);
+ if (pathsEqual && this.oldTree == anOldTree && this.newTree == aNewTree)
+ return delta;
+ return null;
+ }
+ }
+
+ /**
+ * These builders are added to build tables in place of builders that couldn't be instantiated
+ */
+ class MissingBuilder extends IncrementalProjectBuilder {
+ private boolean hasBeenBuilt = false;
+ private String name;
+
+ MissingBuilder(String name) {
+ this.name = name;
+ }
+
+ /**
+ * Log an exception on the first build, and silently do nothing on subsequent builds.
+ */
+ @Override
+ protected IProject[] build(int kind, Map args, IProgressMonitor monitor) {
+ if (!hasBeenBuilt && Policy.DEBUG_BUILD_FAILURE) {
+ hasBeenBuilt = true;
+ String msg = NLS.bind(Messages.events_skippingBuilder, name, getProject().getName());
+ Policy.log(IStatus.WARNING, msg, null);
+ }
+ return null;
+ }
+
+ String getName() {
+ return name;
+ }
+
+ }
+
+ private static final int TOTAL_BUILD_WORK = Policy.totalWork * 1000;
+
+ //the job for performing background autobuild
+ final AutoBuildJob autoBuildJob;
+ private boolean building = false;
+ private final Set builtProjects = new HashSet();
+
+ //the following four fields only apply for the lifetime of a single builder invocation.
+ protected InternalBuilder currentBuilder;
+ private DeltaDataTree currentDelta;
+ private ElementTree currentLastBuiltTree;
+ private ElementTree currentTree;
+
+ /**
+ * Caches the IResourceDelta for a pair of trees
+ */
+ final private DeltaCache deltaCache = new DeltaCache();
+ /**
+ * Caches the DeltaDataTree used to determine if a build is necessary
+ */
+ final private DeltaCache deltaTreeCache = new DeltaCache();
+
+ private ILock lock;
+
+ //used for the build cycle looping mechanism
+ private boolean rebuildRequested = false;
+
+ private final Bundle systemBundle = Platform.getBundle("org.eclipse.osgi"); //$NON-NLS-1$
+
+ //used for debug/trace timing
+ private long timeStamp = -1;
+ private long overallTimeStamp = -1;
+ private Workspace workspace;
+
+ public BuildManager(Workspace workspace, ILock workspaceLock) {
+ this.workspace = workspace;
+ this.autoBuildJob = new AutoBuildJob(workspace);
+ this.lock = workspaceLock;
+ InternalBuilder.buildManager = this;
+ }
+
+ private void basicBuild(int trigger, IncrementalProjectBuilder builder, Map args, MultiStatus status, IProgressMonitor monitor) {
+ try {
+ currentBuilder = builder;
+ //clear any old requests to forget built state
+ currentBuilder.clearLastBuiltStateRequests();
+ // Figure out want kind of build is needed
+ boolean clean = trigger == IncrementalProjectBuilder.CLEAN_BUILD;
+ currentLastBuiltTree = currentBuilder.getLastBuiltTree();
+
+ // Does the build command respond to this trigger?
+ boolean isBuilding = builder.getCommand().isBuilding(trigger);
+
+ // If no tree is available we have to do a full build
+ if (!clean && currentLastBuiltTree == null) {
+ // Bug 306746 - Don't promote build to FULL_BUILD if builder doesn't AUTO_BUILD
+ if (trigger == IncrementalProjectBuilder.AUTO_BUILD && !isBuilding)
+ return;
+ // Without a build tree the build is promoted to FULL_BUILD
+ trigger = IncrementalProjectBuilder.FULL_BUILD;
+ isBuilding = isBuilding || builder.getCommand().isBuilding(trigger);
+ }
+
+ //don't build if this builder doesn't respond to the trigger
+ if (!isBuilding) {
+ if (clean)
+ currentBuilder.setLastBuiltTree(null);
+ return;
+ }
+
+ // For incremental builds, grab a pointer to the current state before computing the delta
+ currentTree = ((trigger == IncrementalProjectBuilder.FULL_BUILD) || clean) ? null : workspace.getElementTree();
+ int depth = -1;
+ ISchedulingRule rule = null;
+ try {
+ //short-circuit if none of the projects this builder cares about have changed.
+ if (!needsBuild(currentBuilder, trigger)) {
+ //use up the progress allocated for this builder
+ monitor.beginTask("", 1); //$NON-NLS-1$
+ monitor.done();
+ return;
+ }
+ rule = builder.getRule(trigger, args);
+ String name = currentBuilder.getLabel();
+ String message;
+ if (name != null)
+ message = NLS.bind(Messages.events_invoking_2, name, builder.getProject().getFullPath());
+ else
+ message = NLS.bind(Messages.events_invoking_1, builder.getProject().getFullPath());
+ monitor.subTask(message);
+ hookStartBuild(builder, trigger);
+ // Make the current tree immutable before releasing the WS lock
+ if (rule != null && currentTree != null)
+ workspace.newWorkingTree();
+ //release workspace lock while calling builders
+ depth = getWorkManager().beginUnprotected();
+ // Acquire the rule required for running this builder
+ if (rule != null) {
+ Job.getJobManager().beginRule(rule, monitor);
+ // Now that we've acquired the rule, changes may have been made concurrently, ensure we're pointing at the
+ // correct currentTree so delta contains concurrent changes made in areas guarded by the scheduling rule
+ if (currentTree != null)
+ currentTree = workspace.getElementTree();
+ }
+ //do the build
+ SafeRunner.run(getSafeRunnable(trigger, args, status, monitor));
+ } finally {
+ // Re-acquire the WS lock, then release the scheduling rule
+ if (depth >= 0)
+ getWorkManager().endUnprotected(depth);
+ if (rule != null)
+ Job.getJobManager().endRule(rule);
+ // Be sure to clean up after ourselves.
+ if (clean || currentBuilder.wasForgetStateRequested()) {
+ currentBuilder.setLastBuiltTree(null);
+ } else if (currentBuilder.wasRememberStateRequested()) {
+ // If remember last build state, and FULL_BUILD
+ // last tree must be set to => null for next build
+ if (trigger == IncrementalProjectBuilder.FULL_BUILD)
+ currentBuilder.setLastBuiltTree(null);
+ // else don't modify the last built tree
+ } else {
+ // remember the current state as the last built state.
+ ElementTree lastTree = workspace.getElementTree();
+ lastTree.immutable();
+ currentBuilder.setLastBuiltTree(lastTree);
+ }
+ hookEndBuild(builder);
+ }
+ } finally {
+ currentBuilder = null;
+ currentTree = null;
+ currentLastBuiltTree = null;
+ currentDelta = null;
+ }
+ }
+
+ protected void basicBuild(IBuildConfiguration buildConfiguration, int trigger, IBuildContext context, ICommand[] commands, MultiStatus status, IProgressMonitor monitor) {
+ try {
+ for (int i = 0; i < commands.length; i++) {
+ checkCanceled(trigger, monitor);
+ BuildCommand command = (BuildCommand) commands[i];
+ IProgressMonitor sub = Policy.subMonitorFor(monitor, 1);
+ IncrementalProjectBuilder builder = getBuilder(buildConfiguration, command, i, status, context);
+ if (builder != null)
+ basicBuild(trigger, builder, command.getArguments(false), status, sub);
+ }
+ } catch (CoreException e) {
+ status.add(e.getStatus());
+ }
+ }
+
+ /**
+ * Runs all builders on the given project config.
+ * @return A status indicating if the build succeeded or failed
+ */
+ private IStatus basicBuild(IBuildConfiguration buildConfiguration, int trigger, IBuildContext context, IProgressMonitor monitor) {
+ if (!canRun(trigger))
+ return Status.OK_STATUS;
+ try {
+ hookStartBuild(new IBuildConfiguration[] {buildConfiguration}, trigger);
+ MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, Messages.events_errors, null);
+ basicBuild(buildConfiguration, trigger, context, status, monitor);
+ return status;
+ } finally {
+ hookEndBuild(trigger);
+ }
+ }
+
+ private void basicBuild(final IBuildConfiguration buildConfiguration, final int trigger, final IBuildContext context, final MultiStatus status, final IProgressMonitor monitor) {
+ try {
+ final IProject project = buildConfiguration.getProject();
+ final ICommand[] commands;
+ if (project.isAccessible())
+ commands = ((Project) project).internalGetDescription().getBuildSpec(false);
+ else
+ commands = null;
+ int work = commands == null ? 0 : commands.length;
+ monitor.beginTask(NLS.bind(Messages.events_building_1, project.getFullPath()), work);
+ if (work == 0)
+ return;
+ ISafeRunnable code = new ISafeRunnable() {
+ @Override
+ public void handleException(Throwable e) {
+ if (e instanceof OperationCanceledException) {
+ if (Policy.DEBUG_BUILD_INVOKING)
+ Policy.debug("Build canceled"); //$NON-NLS-1$
+ throw (OperationCanceledException) e;
+ }
+ // don't log the exception....it is already being logged in Workspace#run
+ // should never get here because the lower-level build code wrappers
+ // builder exceptions in core exceptions if required.
+ String errorText = e.getMessage();
+ if (errorText == null)
+ errorText = NLS.bind(Messages.events_unknown, e.getClass().getName(), project.getName());
+ status.add(new Status(IStatus.WARNING, ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, errorText, e));
+ }
+
+ @Override
+ public void run() throws Exception {
+ basicBuild(buildConfiguration, trigger, context, commands, status, monitor);
+ }
+ };
+ SafeRunner.run(code);
+ } finally {
+ monitor.done();
+ }
+ }
+
+ /**
+ * Runs the builder with the given name on the given project config.
+ * @return A status indicating if the build succeeded or failed
+ */
+ private IStatus basicBuild(IBuildConfiguration buildConfiguration, int trigger, String builderName, Map args, IProgressMonitor monitor) {
+ final IProject project = buildConfiguration.getProject();
+ monitor = Policy.monitorFor(monitor);
+ try {
+ String message = NLS.bind(Messages.events_building_1, project.getFullPath());
+ monitor.beginTask(message, 1);
+ if (!canRun(trigger))
+ return Status.OK_STATUS;
+ try {
+ hookStartBuild(new IBuildConfiguration[] {buildConfiguration}, trigger);
+ MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, Messages.events_errors, null);
+ ICommand command = getCommand(project, builderName, args);
+ try {
+ IBuildContext context = new BuildContext(buildConfiguration);
+ IncrementalProjectBuilder builder = getBuilder(buildConfiguration, command, -1, status, context);
+ if (builder != null)
+ basicBuild(trigger, builder, args, status, Policy.subMonitorFor(monitor, 1));
+ } catch (CoreException e) {
+ status.add(e.getStatus());
+ }
+ return status;
+ } finally {
+ hookEndBuild(trigger);
+ }
+ } finally {
+ monitor.done();
+ }
+ }
+
+ /**
+ * Loop the workspace build until no more builders request a rebuild.
+ */
+ private void basicBuildLoop(IBuildConfiguration[] configs, IBuildConfiguration[] requestedConfigs, int trigger, MultiStatus status, IProgressMonitor monitor) {
+ int projectWork = configs.length;
+ if (projectWork > 0)
+ projectWork = TOTAL_BUILD_WORK / projectWork;
+ int maxIterations = workspace.getDescription().getMaxBuildIterations();
+ if (maxIterations <= 0)
+ maxIterations = 1;
+ rebuildRequested = true;
+ for (int iter = 0; rebuildRequested && iter < maxIterations; iter++) {
+ rebuildRequested = false;
+ builtProjects.clear();
+ for (int i = 0; i < configs.length; i++) {
+ if (configs[i].getProject().isAccessible()) {
+ IBuildContext context = new BuildContext(configs[i], requestedConfigs, configs);
+ basicBuild(configs[i], trigger, context, status, Policy.subMonitorFor(monitor, projectWork));
+ builtProjects.add(configs[i].getProject());
+ }
+ }
+ //subsequent builds should always be incremental
+ trigger = IncrementalProjectBuilder.INCREMENTAL_BUILD;
+ }
+ }
+
+ /**
+ * Runs all builders on all the given project configs, in the order that
+ * they are given.
+ * @return A status indicating if the build succeeded or failed
+ */
+ public IStatus build(IBuildConfiguration[] configs, IBuildConfiguration[] requestedConfigs, int trigger, IProgressMonitor monitor) {
+ monitor = Policy.monitorFor(monitor);
+ try {
+ monitor.beginTask(Messages.events_building_0, TOTAL_BUILD_WORK);
+ if (!canRun(trigger))
+ return Status.OK_STATUS;
+ try {
+ hookStartBuild(configs, trigger);
+ MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.BUILD_FAILED, Messages.events_errors, null);
+ basicBuildLoop(configs, requestedConfigs, trigger, status, monitor);
+ return status;
+ } finally {
+ hookEndBuild(trigger);
+ }
+ } finally {
+ monitor.done();
+ if (trigger == IncrementalProjectBuilder.INCREMENTAL_BUILD || trigger == IncrementalProjectBuilder.FULL_BUILD)
+ autoBuildJob.avoidBuild();
+ }
+ }
+
+ /**
+ * Runs the builder with the given name on the given project config.
+ * @return A status indicating if the build succeeded or failed
+ */
+ public IStatus build(IBuildConfiguration buildConfiguration, int trigger, String builderName, Map args, IProgressMonitor monitor) {
+ monitor = Policy.monitorFor(monitor);
+ if (builderName == null) {
+ IBuildContext context = new BuildContext(buildConfiguration);
+ return basicBuild(buildConfiguration, trigger, context, monitor);
+ }
+ return basicBuild(buildConfiguration, trigger, builderName, args, monitor);
+ }
+
+ private boolean canRun(int trigger) {
+ return !building;
+ }
+
+ /**
+ * Cancel the build if the user has canceled or if an auto-build has been interrupted.
+ */
+ private void checkCanceled(int trigger, IProgressMonitor monitor) {
+ //if the system is shutting down, don't build
+ if (systemBundle.getState() == Bundle.STOPPING)
+ throw new OperationCanceledException();
+ Policy.checkCanceled(monitor);
+ //check for auto-cancel only if we are auto-building
+ if (trigger != IncrementalProjectBuilder.AUTO_BUILD)
+ return;
+ //check for request to interrupt the auto-build
+ if (autoBuildJob.isInterrupted())
+ throw new OperationCanceledException();
+ }
+
+ /**
+ * Creates and returns an ArrayList of BuilderPersistentInfo.
+ * The list includes entries for all builders for all configs that are
+ * in the builder spec, and that have a last built state, even if they
+ * have not been instantiated this session.
+ *
+ * e.g.
+ * For a project with 3 builders, 2 build configurations and the second
+ * builder doesn't support configurations.
+ * The returned List of BuilderInfos is ordered:
+ * builder_id, config_name,builder_index
+ * builder_1, config_1, 1
+ * builder_1, config_2, 1
+ * builder_2, null, 2
+ * builder_3, config_1, 3
+ * builder_3, config_1, 3
+ *
+ */
+ public ArrayList createBuildersPersistentInfo(IProject project) throws CoreException {
+ /* get the old builders (those not yet instantiated) */
+ ArrayList oldInfos = getBuildersPersistentInfo(project);
+
+ ProjectDescription desc = ((Project) project).internalGetDescription();
+ ICommand[] commands = desc.getBuildSpec(false);
+ if (commands.length == 0)
+ return null;
+ IBuildConfiguration[] configs = project.getBuildConfigs();
+
+ /* build the new list */
+ ArrayList newInfos = new ArrayList(commands.length * configs.length);
+ for (int i = 0; i < commands.length; i++) {
+ BuildCommand command = (BuildCommand) commands[i];
+ String builderName = command.getBuilderName();
+
+ // If the builder doesn't support configurations, only 1 delta tree to persist
+ boolean supportsConfigs = command.supportsConfigs();
+ int numberConfigs = supportsConfigs ? configs.length : 1;
+
+ for (int j = 0; j < numberConfigs; j++) {
+ IBuildConfiguration config = configs[j];
+ BuilderPersistentInfo info = null;
+ IncrementalProjectBuilder builder = ((BuildCommand) commands[i]).getBuilder(config);
+ if (builder == null) {
+ // if the builder was not instantiated, use the old info if any.
+ if (oldInfos != null)
+ info = getBuilderInfo(oldInfos, builderName, supportsConfigs ? config.getName() : null, i);
+ } else if (!(builder instanceof MissingBuilder)) {
+ ElementTree oldTree = ((InternalBuilder) builder).getLastBuiltTree();
+ //don't persist build state for builders that have no last built state
+ if (oldTree != null) {
+ // if the builder was instantiated, construct a memento with the important info
+ info = new BuilderPersistentInfo(project.getName(), supportsConfigs ? config.getName() : null, builderName, i);
+ info.setLastBuildTree(oldTree);
+ info.setInterestingProjects(((InternalBuilder) builder).getInterestingProjects());
+ }
+ }
+ if (info != null)
+ newInfos.add(info);
+ }
+ }
+ return newInfos;
+ }
+
+ private String debugBuilder() {
+ return currentBuilder == null ? "" : currentBuilder.getClass().getName(); //$NON-NLS-1$
+ }
+
+ private String debugProject() {
+ if (currentBuilder == null)
+ return ""; //$NON-NLS-1$
+ return currentBuilder.getProject().getFullPath().toString();
+ }
+
+ /**
+ * Returns a string representation of a build trigger for debugging purposes.
+ * @param trigger The trigger to compute a representation of
+ * @return A string describing the trigger.
+ */
+ private String debugTrigger(int trigger) {
+ switch (trigger) {
+ case IncrementalProjectBuilder.FULL_BUILD :
+ return "FULL_BUILD"; //$NON-NLS-1$
+ case IncrementalProjectBuilder.CLEAN_BUILD :
+ return "CLEAN_BUILD"; //$NON-NLS-1$
+ case IncrementalProjectBuilder.AUTO_BUILD :
+ case IncrementalProjectBuilder.INCREMENTAL_BUILD :
+ default :
+ return "INCREMENTAL_BUILD"; //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * The outermost workspace operation has finished. Do an autobuild if necessary.
+ */
+ public void endTopLevel(boolean needsBuild) {
+ autoBuildJob.build(needsBuild);
+ }
+
+ /**
+ * Returns the value of the boolean configuration element attribute with the
+ * given name, or false if the attribute is missing.
+ */
+ private boolean getBooleanAttribute(IConfigurationElement element, String name) {
+ String valueString = element.getAttribute(name);
+ return valueString != null && valueString.equalsIgnoreCase(Boolean.TRUE.toString());
+ }
+
+ /**
+ * Returns the builder instance corresponding to the given command, or
+ * null if the builder was not valid.
+ * @param buildConfiguration The project config this builder corresponds to
+ * @param command The build command
+ * @param buildSpecIndex The index of this builder in the build spec, or -1 if
+ * the index is unknown
+ * @param status MultiStatus for collecting errors
+ */
+ private IncrementalProjectBuilder getBuilder(IBuildConfiguration buildConfiguration, ICommand command, int buildSpecIndex, MultiStatus status) throws CoreException {
+ InternalBuilder result = ((BuildCommand) command).getBuilder(buildConfiguration);
+ if (result == null) {
+ result = initializeBuilder(command.getBuilderName(), buildConfiguration, buildSpecIndex, status);
+ result.setCommand(command);
+ result.setBuildConfig(buildConfiguration);
+ result.startupOnInitialize();
+ ((BuildCommand) command).addBuilder(buildConfiguration, (IncrementalProjectBuilder) result);
+ }
+ // Ensure the build configuration stays fresh for non-config aware builders
+ result.setBuildConfig(buildConfiguration);
+ if (!validateNature(result, command.getBuilderName())) {
+ //skip this builder and null its last built tree because it is invalid
+ //if the nature gets added or re-enabled a full build will be triggered
+ result.setLastBuiltTree(null);
+ return null;
+ }
+ return (IncrementalProjectBuilder) result;
+ }
+
+ /**
+ * Returns the builder instance corresponding to the given command, or
+ * null if the builder was not valid, and sets its context
+ * to the one supplied.
+ *
+ * @param buildConfiguration The project config this builder corresponds to
+ * @param command The build command
+ * @param buildSpecIndex The index of this builder in the build spec, or -1 if
+ * the index is unknown
+ * @param status MultiStatus for collecting errors
+ */
+ private IncrementalProjectBuilder getBuilder(IBuildConfiguration buildConfiguration, ICommand command, int buildSpecIndex, MultiStatus status, IBuildContext context) throws CoreException {
+ InternalBuilder builder = getBuilder(buildConfiguration, command, buildSpecIndex, status);
+ if (builder != null)
+ builder.setContext(context);
+ return (IncrementalProjectBuilder) builder;
+ }
+
+ /**
+ * Removes the builder persistent info from the map corresponding to the
+ * given builder name, configuration name and build spec index, or null if not found
+ *
+ * @param configName or null if the builder doesn't support configurations
+ * @param buildSpecIndex The index in the build spec, or -1 if unknown
+ */
+ private BuilderPersistentInfo getBuilderInfo(ArrayList infos, String builderName, String configName, int buildSpecIndex) {
+ //try to match on builder index, but if not match is found, use the builder name and config name
+ //this is because older workspace versions did not store builder infos in build spec order
+ BuilderPersistentInfo nameMatch = null;
+ for (Iterator it = infos.iterator(); it.hasNext();) {
+ BuilderPersistentInfo info = it.next();
+ // match on name, config name and build spec index if known
+ // Note: the config name may be null for builders that don't support configurations, or old workspaces
+ if (info.getBuilderName().equals(builderName) && (info.getConfigName() == null || info.getConfigName().equals(configName))) {
+ //we have found a match on name alone
+ if (nameMatch == null)
+ nameMatch = info;
+ //see if the index matches
+ if (buildSpecIndex == -1 || info.getBuildSpecIndex() == -1 || buildSpecIndex == info.getBuildSpecIndex())
+ return info;
+ }
+ }
+ //no exact index match, so return name match, if any
+ return nameMatch;
+ }
+
+ /**
+ * Returns a list of BuilderPersistentInfo.
+ * The list includes entries for all builders that are in the builder spec,
+ * and that have a last built state but have not been instantiated this session.
+ */
+ @SuppressWarnings({"unchecked"})
+ public ArrayList getBuildersPersistentInfo(IProject project) throws CoreException {
+ return (ArrayList) project.getSessionProperty(K_BUILD_LIST);
+ }
+
+ /**
+ * Returns a build command for the given builder name and project.
+ * First looks for matching command in the project's build spec. If none
+ * is found, a new command is created and returned. This is necessary
+ * because IProject.build allows a builder to be executed that is not in the
+ * build spec.
+ */
+ private ICommand getCommand(IProject project, String builderName, Map args) {
+ ICommand[] buildSpec = ((Project) project).internalGetDescription().getBuildSpec(false);
+ for (int i = 0; i < buildSpec.length; i++)
+ if (buildSpec[i].getBuilderName().equals(builderName))
+ return buildSpec[i];
+ //none found, so create a new command
+ BuildCommand result = new BuildCommand();
+ result.setBuilderName(builderName);
+ result.setArguments(args);
+ return result;
+ }
+
+ /**
+ * Gets a workspace delta for a given project, based on the state of the workspace
+ * tree the last time the current builder was run.
+ *
+ * Returns null if:
+ *
+ *
The state of the workspace is unknown.
+ *
The current builder has not indicated that it is interested in deltas
+ * for the given project.
+ *
If the project does not exist.
+ *
+ *
+ * Deltas are computed once and cached for efficiency.
+ *
+ * @param project the project to get a delta for
+ */
+ IResourceDelta getDelta(IProject project) {
+ try {
+ lock.acquire();
+ if (currentTree == null) {
+ if (Policy.DEBUG_BUILD_FAILURE)
+ Policy.debug("Build: no tree for delta " + debugBuilder() + " [" + debugProject() + "]"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ return null;
+ }
+ //check if this builder has indicated it cares about this project
+ if (!isInterestingProject(project)) {
+ if (Policy.DEBUG_BUILD_FAILURE)
+ Policy.debug("Build: project not interesting for this builder " + debugBuilder() + " [" + debugProject() + "] " + project.getFullPath()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ return null;
+ }
+ //check if this project has changed
+ if (currentDelta != null && currentDelta.findNodeAt(project.getFullPath()) == null) {
+ //if the project never existed (not in delta and not in current tree), return null
+ if (!project.exists())
+ return null;
+ //just return an empty delta rooted at this project
+ return ResourceDeltaFactory.newEmptyDelta(project);
+ }
+ //now check against the cache
+ IResourceDelta result = (IResourceDelta) deltaCache.getDelta(project.getFullPath(), currentLastBuiltTree, currentTree);
+ if (result != null)
+ return result;
+
+ long startTime = 0L;
+ if (Policy.DEBUG_BUILD_DELTA) {
+ startTime = System.currentTimeMillis();
+ Policy.debug("Computing delta for project: " + project.getName()); //$NON-NLS-1$
+ }
+ result = ResourceDeltaFactory.computeDelta(workspace, currentLastBuiltTree, currentTree, project.getFullPath(), -1);
+ deltaCache.cache(project.getFullPath(), currentLastBuiltTree, currentTree, result);
+ if (Policy.DEBUG_BUILD_FAILURE && result == null)
+ Policy.debug("Build: no delta " + debugBuilder() + " [" + debugProject() + "] " + project.getFullPath()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ if (Policy.DEBUG_BUILD_DELTA)
+ Policy.debug("Finished computing delta, time: " + (System.currentTimeMillis() - startTime) + "ms" + ((ResourceDelta) result).toDeepDebugString()); //$NON-NLS-1$ //$NON-NLS-2$
+ return result;
+ } finally {
+ lock.release();
+ }
+ }
+
+ /**
+ * Returns the safe runnable instance for invoking a builder
+ */
+ private ISafeRunnable getSafeRunnable(final int trigger, final Map args, final MultiStatus status, final IProgressMonitor monitor) {
+ return new ISafeRunnable() {
+ @Override
+ public void handleException(Throwable e) {
+ if (e instanceof OperationCanceledException) {
+ if (Policy.DEBUG_BUILD_INVOKING)
+ Policy.debug("Build canceled"); //$NON-NLS-1$
+ //just discard built state when a builder cancels, to ensure
+ //that it is called again on the very next build.
+ currentBuilder.forgetLastBuiltState();
+ throw (OperationCanceledException) e;
+ }
+ //ResourceStats.buildException(e);
+ // don't log the exception....it is already being logged in SafeRunner#run
+
+ //add a generic message to the MultiStatus
+ String builderName = currentBuilder.getLabel();
+ if (builderName == null || builderName.length() == 0)
+ builderName = currentBuilder.getClass().getName();
+ String pluginId = currentBuilder.getPluginId();
+ String message = NLS.bind(Messages.events_builderError, builderName, currentBuilder.getProject().getName());
+ status.add(new Status(IStatus.ERROR, pluginId, IResourceStatus.BUILD_FAILED, message, e));
+
+ //add the exception status to the MultiStatus
+ if (e instanceof CoreException)
+ status.add(((CoreException) e).getStatus());
+ }
+
+ @Override
+ public void run() throws Exception {
+ IProject[] prereqs = null;
+ //invoke the appropriate build method depending on the trigger
+ if (trigger != IncrementalProjectBuilder.CLEAN_BUILD)
+ prereqs = currentBuilder.build(trigger, args, monitor);
+ else
+ currentBuilder.clean(monitor);
+ if (prereqs == null)
+ prereqs = new IProject[0];
+ currentBuilder.setInterestingProjects(prereqs.clone());
+ }
+ };
+ }
+
+ /**
+ * We know the work manager is always available in the middle of
+ * a build.
+ */
+ private WorkManager getWorkManager() {
+ try {
+ return workspace.getWorkManager();
+ } catch (CoreException e) {
+ //cannot happen
+ }
+ //avoid compile error
+ return null;
+ }
+
+ @Override
+ public void handleEvent(LifecycleEvent event) {
+ IProject project = null;
+ switch (event.kind) {
+ case LifecycleEvent.PRE_PROJECT_DELETE :
+ case LifecycleEvent.PRE_PROJECT_MOVE :
+ project = (IProject) event.resource;
+ //make sure the builder persistent info is deleted for the project move case
+ if (project.isAccessible())
+ setBuildersPersistentInfo(project, null);
+ }
+ }
+
+ /**
+ * Returns true if at least one of the given project's configs have been built
+ * during this build cycle; and false otherwise.
+ */
+ boolean hasBeenBuilt(IProject project) {
+ return builtProjects.contains(project);
+ }
+
+ /**
+ * Hook for adding trace options and debug information at the end of a build.
+ * This hook is called after each builder instance is called.
+ */
+ private void hookEndBuild(IncrementalProjectBuilder builder) {
+ if (ResourceStats.TRACE_BUILDERS)
+ ResourceStats.endBuild();
+ if (!Policy.DEBUG_BUILD_INVOKING || timeStamp == -1)
+ return; //builder wasn't called or we are not debugging
+ Policy.debug("Builder finished: " + toString(builder) + " time: " + (System.currentTimeMillis() - timeStamp) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ timeStamp = -1;
+ }
+
+ /**
+ * Hook for adding trace options and debug information at the end of a build.
+ * This hook is called at the end of a build cycle invoked by calling a
+ * build API method.
+ */
+ private void hookEndBuild(int trigger) {
+ building = false;
+ builtProjects.clear();
+ deltaCache.flush();
+ deltaTreeCache.flush();
+ //ensure autobuild runs after a clean
+ if (trigger == IncrementalProjectBuilder.CLEAN_BUILD)
+ autoBuildJob.forceBuild();
+ if (Policy.DEBUG_BUILD_INVOKING) {
+ Policy.debug("Top-level build-end time: " + (System.currentTimeMillis() - overallTimeStamp)); //$NON-NLS-1$
+ overallTimeStamp = -1;
+ }
+ }
+
+ /**
+ * Hook for adding trace options and debug information at the start of a build.
+ * This hook is called before each builder instance is called.
+ */
+ private void hookStartBuild(IncrementalProjectBuilder builder, int trigger) {
+ if (ResourceStats.TRACE_BUILDERS)
+ ResourceStats.startBuild(builder);
+ if (Policy.DEBUG_BUILD_INVOKING) {
+ timeStamp = System.currentTimeMillis();
+ Policy.debug("Invoking (" + debugTrigger(trigger) + ") on builder: " + toString(builder)); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ }
+
+ /**
+ * Hook for adding trace options and debug information at the start of a build.
+ * This hook is called when a build API method is called, before any builders
+ * start running.
+ */
+ private void hookStartBuild(IBuildConfiguration[] configs, int trigger) {
+ building = true;
+ if (Policy.DEBUG_BUILD_STACK)
+ Policy.debug(new RuntimeException("Starting build: " + debugTrigger(trigger))); //$NON-NLS-1$
+ if (Policy.DEBUG_BUILD_INVOKING) {
+ overallTimeStamp = System.currentTimeMillis();
+ StringBuffer sb = new StringBuffer("Top-level build-start of: "); //$NON-NLS-1$
+ for (int i = 0; i < configs.length; i++)
+ sb.append(configs[i]).append(", "); //$NON-NLS-1$
+ sb.append(debugTrigger(trigger));
+ Policy.debug(sb.toString());
+ }
+ }
+
+ /**
+ * Instantiates the builder with the given name. If the builder, its plugin, or its nature
+ * is missing, create a placeholder builder to takes its place. This is needed to generate
+ * appropriate exceptions when somebody tries to invoke the builder, and to
+ * prevent trying to instantiate it every time a build is run.
+ * This method NEVER returns null.
+ */
+ private IncrementalProjectBuilder initializeBuilder(String builderName, IBuildConfiguration buildConfiguration, int buildSpecIndex, MultiStatus status) throws CoreException {
+ IProject project = buildConfiguration.getProject();
+ IncrementalProjectBuilder builder = null;
+ try {
+ builder = instantiateBuilder(builderName);
+ } catch (CoreException e) {
+ status.add(new ResourceStatus(IResourceStatus.BUILD_FAILED, project.getFullPath(), NLS.bind(Messages.events_instantiate_1, builderName), e));
+ status.add(e.getStatus());
+ }
+ if (builder == null) {
+ //unable to create the builder, so create a placeholder to fill in for it
+ builder = new MissingBuilder(builderName);
+ }
+ // get the map of builders to get the last built tree
+ ArrayList infos = getBuildersPersistentInfo(project);
+ if (infos != null) {
+ BuilderPersistentInfo info = getBuilderInfo(infos, builderName, buildConfiguration.getName(), buildSpecIndex);
+ if (info != null) {
+ infos.remove(info);
+ ElementTree tree = info.getLastBuiltTree();
+ if (tree != null)
+ ((InternalBuilder) builder).setLastBuiltTree(tree);
+ ((InternalBuilder) builder).setInterestingProjects(info.getInterestingProjects());
+ }
+ // delete the build map if it's now empty
+ if (infos.size() == 0)
+ setBuildersPersistentInfo(project, null);
+ }
+ return builder;
+ }
+
+ /**
+ * Instantiates and returns the builder with the given name. If the builder, its plugin, or its nature
+ * is missing, returns null.
+ */
+ private IncrementalProjectBuilder instantiateBuilder(String builderName) throws CoreException {
+ IExtension extension = Platform.getExtensionRegistry().getExtension(ResourcesPlugin.PI_RESOURCES, ResourcesPlugin.PT_BUILDERS, builderName);
+ if (extension == null)
+ return null;
+ IConfigurationElement[] configs = extension.getConfigurationElements();
+ if (configs.length == 0)
+ return null;
+ String natureId = null;
+ if (getBooleanAttribute(configs[0], "hasNature")) { //$NON-NLS-1$
+ //find the nature that owns this builder
+ String builderId = extension.getUniqueIdentifier();
+ natureId = workspace.getNatureManager().findNatureForBuilder(builderId);
+ if (natureId == null)
+ return null;
+ }
+ //The nature exists, or this builder doesn't specify a nature
+ InternalBuilder builder = (InternalBuilder) configs[0].createExecutableExtension("run"); //$NON-NLS-1$
+ builder.setPluginId(extension.getContributor().getName());
+ builder.setLabel(extension.getLabel());
+ builder.setNatureId(natureId);
+ builder.setCallOnEmptyDelta(getBooleanAttribute(configs[0], "callOnEmptyDelta")); //$NON-NLS-1$
+ return (IncrementalProjectBuilder) builder;
+ }
+
+ /**
+ * Another thread is attempting to modify the workspace. Cancel the
+ * autobuild and wait until it completes.
+ */
+ public void interrupt() {
+ autoBuildJob.interrupt();
+ }
+
+ /**
+ * Returns whether an autobuild is pending (requested but not yet completed).
+ */
+ public boolean isAutobuildBuildPending() {
+ return autoBuildJob.getState() != Job.NONE;
+
+ }
+
+ /**
+ * Returns true if the current builder is interested in changes
+ * to the given project, and false otherwise.
+ */
+ private boolean isInterestingProject(IProject project) {
+ if (project.equals(currentBuilder.getProject()))
+ return true;
+ IProject[] interestingProjects = currentBuilder.getInterestingProjects();
+ for (int i = 0; i < interestingProjects.length; i++) {
+ if (interestingProjects[i].equals(project)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if the given builder needs to be invoked, and false
+ * otherwise.
+ *
+ * The algorithm is to compute the intersection of the set of build configs that
+ * have changed since the last build, and the set of build configs this builder
+ * cares about. This is an optimization, under the assumption that computing
+ * the forward delta once (not the resource delta) is more efficient than
+ * computing project deltas and invoking builders for projects that haven't
+ * changed.
+ */
+ private boolean needsBuild(InternalBuilder builder, int trigger) {
+ //on some triggers we build regardless of the delta
+ switch (trigger) {
+ case IncrementalProjectBuilder.CLEAN_BUILD :
+ return true;
+ case IncrementalProjectBuilder.FULL_BUILD :
+ return true;
+ case IncrementalProjectBuilder.INCREMENTAL_BUILD :
+ if (currentBuilder.callOnEmptyDelta())
+ return true;
+ //fall through and check if there is a delta
+ }
+
+ //compute the delta since the last built state
+ ElementTree oldTree = builder.getLastBuiltTree();
+ ElementTree newTree = workspace.getElementTree();
+ long start = System.currentTimeMillis();
+ currentDelta = (DeltaDataTree) deltaTreeCache.getDelta(null, oldTree, newTree);
+ if (currentDelta == null) {
+ if (Policy.DEBUG_BUILD_NEEDED) {
+ String message = "Checking if need to build. Starting delta computation between: " + oldTree.toString() + " and " + newTree.toString(); //$NON-NLS-1$ //$NON-NLS-2$
+ Policy.debug(message);
+ }
+ currentDelta = newTree.getDataTree().forwardDeltaWith(oldTree.getDataTree(), ResourceComparator.getBuildComparator());
+ if (Policy.DEBUG_BUILD_NEEDED)
+ Policy.debug("End delta computation. (" + (System.currentTimeMillis() - start) + "ms)."); //$NON-NLS-1$ //$NON-NLS-2$
+ deltaTreeCache.cache(null, oldTree, newTree, currentDelta);
+ }
+
+ //search for the builder's project
+ if (currentDelta.findNodeAt(builder.getProject().getFullPath()) != null) {
+ if (Policy.DEBUG_BUILD_NEEDED)
+ Policy.debug(toString(builder) + " needs building because of changes in: " + builder.getProject().getName()); //$NON-NLS-1$
+ return true;
+ }
+
+ //search for builder's interesting projects
+ IProject[] projects = builder.getInterestingProjects();
+ for (int i = 0; i < projects.length; i++) {
+ if (currentDelta.findNodeAt(projects[i].getFullPath()) != null) {
+ if (Policy.DEBUG_BUILD_NEEDED)
+ Policy.debug(toString(builder) + " needs building because of changes in: " + projects[i].getName()); //$NON-NLS-1$
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Removes all builders with the given ID from the build spec.
+ * Does nothing if there were no such builders in the spec
+ */
+ private void removeBuilders(IProject project, String builderId) throws CoreException {
+ IProjectDescription desc = project.getDescription();
+ ICommand[] oldSpec = desc.getBuildSpec();
+ int oldLength = oldSpec.length;
+ if (oldLength == 0)
+ return;
+ int remaining = 0;
+ //null out all commands that match the builder to remove
+ for (int i = 0; i < oldSpec.length; i++) {
+ if (oldSpec[i].getBuilderName().equals(builderId))
+ oldSpec[i] = null;
+ else
+ remaining++;
+ }
+ //check if any were actually removed
+ if (remaining == oldSpec.length)
+ return;
+ ICommand[] newSpec = new ICommand[remaining];
+ for (int i = 0, newIndex = 0; i < oldLength; i++) {
+ if (oldSpec[i] != null)
+ newSpec[newIndex++] = oldSpec[i];
+ }
+ desc.setBuildSpec(newSpec);
+ project.setDescription(desc, IResource.NONE, null);
+ }
+
+ /**
+ * Hook for builders to request a rebuild.
+ */
+ void requestRebuild() {
+ rebuildRequested = true;
+ }
+
+ /**
+ * Sets the builder infos for the given build config. The builder infos are
+ * an ArrayList of BuilderPersistentInfo.
+ * The list includes entries for all builders that are
+ * in the builder spec, and that have a last built state, even if they
+ * have not been instantiated this session.
+ */
+ public void setBuildersPersistentInfo(IProject project, List list) {
+ try {
+ project.setSessionProperty(K_BUILD_LIST, list);
+ } catch (CoreException e) {
+ //project is missing -- build state will be lost
+ //can't throw an exception because this happens on startup
+ Policy.log(new ResourceStatus(IStatus.ERROR, 1, project.getFullPath(), "Project missing in setBuildersPersistentInfo", null)); //$NON-NLS-1$
+ }
+ }
+
+ @Override
+ public void shutdown(IProgressMonitor monitor) {
+ autoBuildJob.cancel();
+ }
+
+ @Override
+ public void startup(IProgressMonitor monitor) {
+ workspace.addLifecycleListener(this);
+ }
+
+ /**
+ * Returns a string representation of the given builder.
+ * For debugging purposes only.
+ */
+ private String toString(InternalBuilder builder) {
+ String name = builder.getClass().getName();
+ name = name.substring(name.lastIndexOf('.') + 1);
+ if (builder instanceof MissingBuilder)
+ name = name + ": '" + ((MissingBuilder) builder).getName() + "'"; //$NON-NLS-1$ //$NON-NLS-2$
+ return name + "(" + builder.getBuildConfig() + ")"; //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ /**
+ * Returns true if the nature membership rules are satisfied for the given
+ * builder extension on the given project, and false otherwise. A builder that
+ * does not specify that it belongs to a nature is always valid. A builder
+ * extension that belongs to a nature can be invalid for the following reasons:
+ *
+ *
The nature that owns the builder does not exist on the given project
+ *
The nature that owns the builder is disabled on the given project
+ *
+ * Furthermore, if the nature that owns the builder does not exist on the project,
+ * that builder will be removed from the build spec.
+ *
+ * Note: This method only validates nature constraints that can vary at runtime.
+ * Additional checks are done in the instantiateBuilder method for constraints
+ * that cannot vary once the plugin registry is initialized.
+ */
+ private boolean validateNature(InternalBuilder builder, String builderId) throws CoreException {
+ String nature = builder.getNatureId();
+ if (nature == null)
+ return true;
+ IProject project = builder.getProject();
+ if (!project.hasNature(nature)) {
+ //remove this builder from the build spec
+ removeBuilders(project, builderId);
+ return false;
+ }
+ return project.isNatureEnabled(nature);
+ }
+
+ /**
+ * Returns the scheduling rule that is required for building the project.
+ */
+ public ISchedulingRule getRule(IBuildConfiguration buildConfiguration, int trigger, String builderName, Map args) {
+ IProject project = buildConfiguration.getProject();
+ MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, Messages.events_errors, null);
+ if (builderName == null) {
+ final ICommand[] commands;
+ if (project.isAccessible()) {
+ Set rules = new HashSet();
+ commands = ((Project) project).internalGetDescription().getBuildSpec(false);
+ boolean hasNullBuildRule = false;
+ BuildContext context = new BuildContext(buildConfiguration);
+ for (int i = 0; i < commands.length; i++) {
+ BuildCommand command = (BuildCommand) commands[i];
+ try {
+ IncrementalProjectBuilder builder = getBuilder(buildConfiguration, command, i, status, context);
+ if (builder != null) {
+ ISchedulingRule builderRule = builder.getRule(trigger, args);
+ if (builderRule != null)
+ rules.add(builderRule);
+ else
+ hasNullBuildRule = true;
+ }
+ } catch (CoreException e) {
+ status.add(e.getStatus());
+ }
+ }
+ if (rules.isEmpty())
+ return null;
+ // Bug 306824 - Builders returning a null rule can't work safely if other builders require a non-null rule
+ // Be pessimistic and fall back to the default build rule (workspace root) in this case.
+ if (!hasNullBuildRule)
+ return new MultiRule(rules.toArray(new ISchedulingRule[rules.size()]));
+ }
+ } else {
+ // Returns the derived resources for the specified builderName
+ ICommand command = getCommand(project, builderName, args);
+ try {
+ IncrementalProjectBuilder builder = getBuilder(buildConfiguration, command, -1, status);
+ if (builder != null)
+ return builder.getRule(trigger, args);
+
+ } catch (CoreException e) {
+ status.add(e.getStatus());
+ }
+ }
+ // Log any errors
+ if (!status.isOK())
+ Policy.log(status);
+ return workspace.getRoot();
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuilderPersistentInfo.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuilderPersistentInfo.java
new file mode 100644
index 0000000000..7610f6d47d
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuilderPersistentInfo.java
@@ -0,0 +1,81 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * Broadcom Corporation - build configurations
+ *******************************************************************************/
+package org.eclipse.core.internal.events;
+
+import org.eclipse.core.internal.resources.ICoreConstants;
+import org.eclipse.core.internal.watson.ElementTree;
+import org.eclipse.core.resources.IProject;
+
+public class BuilderPersistentInfo {
+ protected String builderName;
+ /**
+ * Index of this builder in the build spec. A value of -1 indicates
+ * that this index is unknown (it was not serialized in older workspace versions).
+ */
+ private int buildSpecIndex = -1;
+ protected IProject[] interestingProjects = ICoreConstants.EMPTY_PROJECT_ARRAY;
+ protected ElementTree lastBuildTree;
+ protected String projectName;
+ protected String configName;
+
+ public BuilderPersistentInfo(String projectName, String builderName, int buildSpecIndex) {
+ this(projectName, null, builderName, buildSpecIndex);
+ }
+
+ public BuilderPersistentInfo(String projectName, String configName, String builderName, int buildSpecIndex) {
+ this.projectName = projectName;
+ this.configName = configName;
+ this.builderName = builderName;
+ this.buildSpecIndex = buildSpecIndex;
+ }
+
+ public String getBuilderName() {
+ return builderName;
+ }
+
+ public int getBuildSpecIndex() {
+ return buildSpecIndex;
+ }
+
+ /**
+ * @return the name of the configuration for which this information refers.
+ * Will return null if the build command doesn't support configurations, or the
+ * build persistent info has been loaded from a workspace without configurations.
+ */
+ public String getConfigName() {
+ return configName;
+ }
+
+ public IProject[] getInterestingProjects() {
+ return interestingProjects;
+ }
+
+ public ElementTree getLastBuiltTree() {
+ return lastBuildTree;
+ }
+
+ public String getProjectName() {
+ return projectName;
+ }
+
+ public void setConfigName(String configName) {
+ this.configName = configName;
+ }
+
+ public void setInterestingProjects(IProject[] projects) {
+ interestingProjects = projects;
+ }
+
+ public void setLastBuildTree(ElementTree tree) {
+ lastBuildTree = tree;
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ILifecycleListener.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ILifecycleListener.java
new file mode 100644
index 0000000000..9196899cba
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ILifecycleListener.java
@@ -0,0 +1,21 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2005 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.events;
+
+import org.eclipse.core.runtime.CoreException;
+
+/**
+ * Interface for clients interested in receiving notification of workspace
+ * lifecycle events.
+ */
+public interface ILifecycleListener {
+ public void handleEvent(LifecycleEvent event) throws CoreException;
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/InternalBuilder.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/InternalBuilder.java
new file mode 100644
index 0000000000..c004301dd3
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/InternalBuilder.java
@@ -0,0 +1,248 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2012 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * Broadcom Corporation - build configurations and references
+ *******************************************************************************/
+package org.eclipse.core.internal.events;
+
+import java.util.Map;
+import org.eclipse.core.internal.resources.ICoreConstants;
+import org.eclipse.core.internal.watson.ElementTree;
+import org.eclipse.core.resources.*;
+import org.eclipse.core.runtime.*;
+
+/**
+ * This class is the internal basis for all builders. Plugin developers should not
+ * subclass this class.
+ *
+ * @see IncrementalProjectBuilder
+ */
+public abstract class InternalBuilder {
+ /**
+ * Hold a direct reference to the build manager as an optimization.
+ * This will be initialized by BuildManager when it is constructed.
+ */
+ static BuildManager buildManager;
+ private ICommand command;
+ private boolean forgetStateRequested = false;
+ private boolean rememberStateRequested = false;
+ private IProject[] interestingProjects = ICoreConstants.EMPTY_PROJECT_ARRAY;
+ /**
+ * Human readable builder name for progress reporting.
+ */
+ private String label;
+ private String natureId;
+ private ElementTree oldState;
+ /**
+ * The symbolic name of the plugin that defines this builder
+ */
+ private String pluginId;
+ /**
+ * The build configuration that this builder is to build.
+ */
+ private IBuildConfiguration buildConfiguration;
+ /**
+ * The context in which the builder was called.
+ */
+ private IBuildContext context = null;
+
+ /**
+ * The value of the callOnEmptyDelta builder extension attribute.
+ */
+ private boolean callOnEmptyDelta = false;
+
+ /*
+ * @see IncrementalProjectBuilder#build
+ */
+ protected abstract IProject[] build(int kind, Map args, IProgressMonitor monitor) throws CoreException;
+
+ /**
+ * Returns the value of the callOnEmptyDelta builder extension attribute.
+ */
+ final boolean callOnEmptyDelta() {
+ return callOnEmptyDelta;
+ }
+ /*
+ * @see IncrementalProjectBuilder
+ */
+ protected abstract void clean(IProgressMonitor monitor) throws CoreException;
+
+ /**
+ * Clears the requests for forgetting or remembering last built states.
+ */
+ final void clearLastBuiltStateRequests() {
+ forgetStateRequested = false;
+ rememberStateRequested = false;
+ }
+
+ /*
+ * @see IncrementalProjectBuilder#forgetLastBuiltState
+ */
+ protected void forgetLastBuiltState() {
+ oldState = null;
+ forgetStateRequested = true;
+ rememberStateRequested = false;
+ }
+
+ /*
+ * @see IncrementalProjectBuilder#rememberLastBuiltState
+ */
+ protected void rememberLastBuiltState() {
+ rememberStateRequested = !forgetStateRequested;
+ }
+
+ /*
+ * @see IncrementalProjectBuilder#getCommand
+ */
+ protected ICommand getCommand() {
+ return (ICommand)((BuildCommand)command).clone();
+ }
+
+ /**
+ * @see IncrementalProjectBuilder#forgetLastBuiltState()
+ * @see IncrementalProjectBuilder#rememberLastBuiltState()
+ */
+ protected IResourceDelta getDelta(IProject aProject) {
+ return buildManager.getDelta(aProject);
+ }
+
+ /**
+ * @see IncrementalProjectBuilder#getContext()
+ */
+ protected IBuildContext getContext() {
+ return context;
+ }
+
+ final IProject[] getInterestingProjects() {
+ return interestingProjects;
+ }
+
+ final String getLabel() {
+ return label;
+ }
+
+ final ElementTree getLastBuiltTree() {
+ return oldState;
+ }
+
+ /**
+ * Returns the ID of the nature that owns this builder. Returns null if the
+ * builder does not belong to a nature.
+ */
+ final String getNatureId() {
+ return natureId;
+ }
+
+ final String getPluginId() {
+ return pluginId;
+ }
+
+ /**
+ * Returns the project for this builder
+ */
+ protected IProject getProject() {
+ return buildConfiguration.getProject();
+ }
+
+ /**
+ * @see IncrementalProjectBuilder#getBuildConfig()
+ */
+ protected IBuildConfiguration getBuildConfig() {
+ return buildConfiguration;
+ }
+
+ /*
+ * @see IncrementalProjectBuilder#hasBeenBuilt
+ */
+ protected boolean hasBeenBuilt(IProject aProject) {
+ return buildManager.hasBeenBuilt(aProject);
+ }
+
+ /*
+ * @see IncrementalProjectBuilder#isInterrupted
+ */
+ public boolean isInterrupted() {
+ return buildManager.autoBuildJob.isInterrupted();
+ }
+
+ /*
+ * @see IncrementalProjectBuilder#needRebuild
+ */
+ protected void needRebuild() {
+ buildManager.requestRebuild();
+ }
+
+ final void setCallOnEmptyDelta(boolean value) {
+ this.callOnEmptyDelta = value;
+ }
+
+ final void setCommand(ICommand value) {
+ this.command = value;
+ }
+
+ final void setInterestingProjects(IProject[] value) {
+ interestingProjects = value;
+ }
+
+ final void setLabel(String value) {
+ this.label = value;
+ }
+
+ final void setLastBuiltTree(ElementTree value) {
+ oldState = value;
+ }
+
+ final void setNatureId(String id) {
+ this.natureId = id;
+ }
+
+ final void setPluginId(String value) {
+ pluginId = value;
+ }
+
+ /**
+ * Sets the build configuration for which this builder operates.
+ * @see #getBuildConfig()
+ */
+ final void setBuildConfig(IBuildConfiguration value) {
+ Assert.isNotNull(value);
+ buildConfiguration = value;
+ if (context == null)
+ context = new BuildContext(buildConfiguration);
+ }
+
+ /**
+ * Sets the context in which the builder was last called.
+ * @see #getContext()
+ */
+ final void setContext(IBuildContext context) {
+ this.context = context;
+ }
+
+ /*
+ * @see IncrementalProjectBuilder#startupOnInitialize
+ */
+ protected abstract void startupOnInitialize();
+
+ /**
+ * Returns true if the builder requested that its last built state be
+ * forgotten, and false otherwise.
+ */
+ final boolean wasForgetStateRequested() {
+ return forgetStateRequested;
+ }
+
+ /**
+ * Returns true if the builder requested that its last built state be
+ * remembered, and false otherwise.
+ */
+ final boolean wasRememberStateRequested() {
+ return rememberStateRequested;
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/LifecycleEvent.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/LifecycleEvent.java
new file mode 100644
index 0000000000..20ad0c7542
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/LifecycleEvent.java
@@ -0,0 +1,91 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * Serge Beauchamp (Freescale Semiconductor) - [229633] Group Support
+ *******************************************************************************/
+package org.eclipse.core.internal.events;
+
+import org.eclipse.core.resources.IResource;
+
+/**
+ * Class used for broadcasting internal workspace lifecycle events. There is a
+ * singleton instance, so no listener is allowed to keep references to the event
+ * after the notification is finished.
+ */
+public class LifecycleEvent {
+ //constants for kinds of internal workspace lifecycle events
+ public static final int PRE_PROJECT_CLOSE = 0x01;
+ public static final int POST_PROJECT_CHANGE = 0x02;
+ public static final int PRE_PROJECT_COPY = 0x04;
+ public static final int PRE_PROJECT_CREATE = 0x08;
+
+ public static final int PRE_PROJECT_DELETE = 0x10;
+ public static final int PRE_PROJECT_OPEN = 0x20;
+ public static final int PRE_PROJECT_MOVE = 0x40;
+
+ public static final int PRE_LINK_COPY = 0x100;
+ public static final int PRE_LINK_CREATE = 0x200;
+ public static final int PRE_LINK_DELETE = 0x400;
+ public static final int PRE_LINK_MOVE = 0x800;
+ public static final int PRE_REFRESH = 0x1000;
+
+ public static final int PRE_GROUP_COPY = 0x2000;
+ public static final int PRE_GROUP_CREATE = 0x4000;
+ public static final int PRE_GROUP_DELETE = 0x8000;
+ public static final int PRE_GROUP_MOVE = 0x10000;
+
+ public static final int PRE_FILTER_ADD = 0x20000;
+ public static final int PRE_FILTER_REMOVE = 0x40000;
+
+ public static final int PRE_LINK_CHANGE = 0x80000;
+
+ /**
+ * The kind of event
+ */
+ public int kind;
+ /**
+ * For events that only involve one resource, this is it. More
+ * specifically, this is used for all events that don't involve a more or
+ * copy. For copy/move events, this resource represents the source of the
+ * copy/move.
+ */
+ public IResource resource;
+ /**
+ * For copy/move events, this resource represents the destination of the
+ * copy/move.
+ */
+ public IResource newResource;
+
+ /**
+ * The update flags for the event.
+ */
+ public int updateFlags;
+
+ private static final LifecycleEvent instance = new LifecycleEvent();
+
+ private LifecycleEvent() {
+ super();
+ }
+
+ public static LifecycleEvent newEvent(int kind, IResource resource) {
+ instance.kind = kind;
+ instance.resource = resource;
+ instance.newResource = null;
+ instance.updateFlags = 0;
+ return instance;
+ }
+
+ public static LifecycleEvent newEvent(int kind, IResource oldResource, IResource newResource, int updateFlags) {
+ instance.kind = kind;
+ instance.resource = oldResource;
+ instance.newResource = newResource;
+ instance.updateFlags = updateFlags;
+ return instance;
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/NodeIDMap.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/NodeIDMap.java
new file mode 100644
index 0000000000..43382c64b9
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/NodeIDMap.java
@@ -0,0 +1,221 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2005 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.events;
+
+import org.eclipse.core.runtime.IPath;
+
+/**
+ * A specialized map that maps Node IDs to their old and new paths.
+ * Used for calculating moves during resource change notification.
+ */
+public class NodeIDMap {
+ //using prime table sizes improves our hash function
+ private static final int[] SIZES = new int[] {13, 29, 71, 173, 349, 733, 1511, 3079, 6133, 16381, 32653, 65543, 131111, 262139, 524287, 1051601};
+ private static final double LOAD_FACTOR = 0.75;
+ //2^32 * golden ratio
+ private static final long LARGE_NUMBER = 2654435761L;
+
+ int sizeOffset = 0;
+ protected int elementCount = 0;
+ protected long[] ids;
+ protected IPath[] oldPaths;
+ protected IPath[] newPaths;
+
+ /**
+ * Creates a new node ID map of default capacity.
+ */
+ public NodeIDMap() {
+ this.sizeOffset = 0;
+ this.ids = new long[SIZES[sizeOffset]];
+ this.oldPaths = new IPath[SIZES[sizeOffset]];
+ this.newPaths = new IPath[SIZES[sizeOffset]];
+ }
+
+ /**
+ * The array isn't large enough so double its size and rehash
+ * all its current values.
+ */
+ protected void expand() {
+ int newLength;
+ try {
+ newLength = SIZES[++sizeOffset];
+ } catch (ArrayIndexOutOfBoundsException e) {
+ //will only occur if there are > 1 million elements in delta
+ newLength = ids.length * 2;
+ }
+ long[] grownIds = new long[newLength];
+ IPath[] grownOldPaths = new IPath[newLength];
+ IPath[] grownNewPaths = new IPath[newLength];
+ int maxArrayIndex = newLength - 1;
+ for (int i = 0; i < ids.length; i++) {
+ long id = ids[i];
+ if (id != 0) {
+ int hash = hashFor(id, newLength);
+ while (grownIds[hash] != 0) {
+ hash++;
+ if (hash > maxArrayIndex)
+ hash = 0;
+ }
+ grownIds[hash] = id;
+ grownOldPaths[hash] = oldPaths[i];
+ grownNewPaths[hash] = newPaths[i];
+ }
+ }
+ ids = grownIds;
+ oldPaths = grownOldPaths;
+ newPaths = grownNewPaths;
+ }
+
+ /**
+ * Returns the index of the given element in the map. If not
+ * found, returns -1.
+ */
+ private int getIndex(long searchID) {
+ final int len = ids.length;
+ int hash = hashFor(searchID, len);
+
+ // search the last half of the array
+ for (int i = hash; i < len; i++) {
+ if (ids[i] == searchID)
+ return i;
+ // marker info not found so return -1
+ if (ids[i] == 0)
+ return -1;
+ }
+
+ // search the beginning of the array
+ for (int i = 0; i < hash - 1; i++) {
+ if (ids[i] == searchID)
+ return i;
+ // marker info not found so return -1
+ if (ids[i] == 0)
+ return -1;
+ }
+ // marker info not found so return -1
+ return -1;
+ }
+
+ /**
+ * Returns the new path location for the given ID, or null
+ * if no new path is available.
+ */
+ public IPath getNewPath(long nodeID) {
+ int index = getIndex(nodeID);
+ if (index == -1)
+ return null;
+ return newPaths[index];
+ }
+
+ /**
+ * Returns the old path location for the given ID, or null
+ * if no old path is available.
+ */
+ public IPath getOldPath(long nodeID) {
+ int index = getIndex(nodeID);
+ if (index == -1)
+ return null;
+ return oldPaths[index];
+ }
+
+ private int hashFor(long id, int size) {
+ //Knuth's hash function from Art of Computer Programming section 6.4
+ return (int) Math.abs((id * LARGE_NUMBER) % size);
+ }
+
+ /**
+ * Returns true if there are no elements in the map, and
+ * false otherwise.
+ */
+ public boolean isEmpty() {
+ return elementCount == 0;
+ }
+
+ /**
+ * Adds the given path mappings to the map. If either oldPath
+ * or newPath is null, they are ignored (old map values are not overwritten).
+ */
+ private void put(long id, IPath oldPath, IPath newPath) {
+ if (oldPath == null && newPath == null)
+ return;
+ int hash = hashFor(id, ids.length);
+
+ // search for an empty slot at the end of the array
+ for (int i = hash; i < ids.length; i++) {
+ if (ids[i] == id) {
+ //replace value for existing entry
+ if (oldPath != null)
+ oldPaths[i] = oldPath;
+ if (newPath != null)
+ newPaths[i] = newPath;
+ return;
+ }
+ if (ids[i] == 0) {
+ //add a new entry to the map
+ ids[i] = id;
+ if (oldPath != null)
+ oldPaths[i] = oldPath;
+ if (newPath != null)
+ newPaths[i] = newPath;
+ elementCount++;
+ // grow if necessary
+ if (shouldGrow())
+ expand();
+ return;
+ }
+ }
+
+ // search for an empty slot at the beginning of the array
+ for (int i = 0; i < hash - 1; i++) {
+ if (ids[i] == id) {
+ //replace value for existing entry
+ if (oldPath != null)
+ oldPaths[i] = oldPath;
+ if (newPath != null)
+ newPaths[i] = newPath;
+ return;
+ }
+ if (ids[i] == 0) {
+ //add a new entry to the map
+ ids[i] = id;
+ if (oldPath != null)
+ oldPaths[i] = oldPath;
+ if (newPath != null)
+ newPaths[i] = newPath;
+ elementCount++;
+ // grow if necessary
+ if (shouldGrow())
+ expand();
+ return;
+ }
+ }
+ // if we didn't find a free slot, then try again with the expanded set
+ expand();
+ put(id, oldPath, newPath);
+ }
+
+ /**
+ * Adds an entry for a node's old path
+ */
+ public void putOldPath(long id, IPath path) {
+ put(id, path, null);
+ }
+
+ /**
+ * Adds an entry for a node's old path
+ */
+ public void putNewPath(long id, IPath path) {
+ put(id, null, path);
+ }
+
+ private boolean shouldGrow() {
+ return elementCount > ids.length * LOAD_FACTOR;
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/NotificationManager.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/NotificationManager.java
new file mode 100644
index 0000000000..3b2ca9ba87
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/NotificationManager.java
@@ -0,0 +1,341 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2015 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * James Blackburn (Broadcom Corp.) - ongoing development
+ *******************************************************************************/
+package org.eclipse.core.internal.events;
+
+import java.util.*;
+import org.eclipse.core.internal.resources.*;
+import org.eclipse.core.internal.utils.Messages;
+import org.eclipse.core.internal.utils.Policy;
+import org.eclipse.core.internal.watson.ElementTree;
+import org.eclipse.core.resources.*;
+import org.eclipse.core.runtime.*;
+import org.eclipse.core.runtime.jobs.Job;
+
+public class NotificationManager implements IManager, ILifecycleListener {
+ class NotifyJob extends Job {
+ private final IWorkspaceRunnable noop = new IWorkspaceRunnable() {
+ @Override
+ public void run(IProgressMonitor monitor) {
+ // do nothing
+ }
+ };
+
+ public NotifyJob() {
+ super(Messages.resources_updating);
+ setSystem(true);
+ }
+
+ @Override
+ public IStatus run(IProgressMonitor monitor) {
+ if (monitor.isCanceled())
+ return Status.CANCEL_STATUS;
+ notificationRequested = true;
+ try {
+ workspace.run(noop, null, IResource.NONE, null);
+ } catch (CoreException e) {
+ return e.getStatus();
+ }
+ return Status.OK_STATUS;
+ }
+ }
+
+ private static final long NOTIFICATION_DELAY = 1500;
+ /**
+ * The Threads that are currently avoiding notification.
+ */
+ private final Set avoidNotify = Collections.synchronizedSet(new HashSet());
+
+ /**
+ * Indicates whether a notification is currently in progress. Used to avoid
+ * causing a notification to be requested as a result of another notification.
+ */
+ protected boolean isNotifying;
+
+ // if there are no changes between the current tree and the last delta state then we
+ // can reuse the lastDelta (if any). If the lastMarkerChangeId is different then the current
+ // one then we have to update that delta with new marker change info
+ /**
+ * last delta we broadcast
+ */
+ private ResourceDelta lastDelta;
+ /**
+ * the marker change Id the last time we computed a delta
+ */
+ private long lastDeltaId;
+ /**
+ * tree the last time we computed a delta
+ */
+ private ElementTree lastDeltaState;
+ protected long lastNotifyDuration = 0L;
+ /**
+ * the marker change id at the end of the last POST_AUTO_BUILD
+ */
+ private long lastPostBuildId = 0;
+ /**
+ * The state of the workspace at the end of the last POST_BUILD
+ * notification
+ */
+ private ElementTree lastPostBuildTree;
+ /**
+ * the marker change id at the end of the last POST_CHANGE
+ */
+ private long lastPostChangeId = 0;
+ /**
+ * The state of the workspace at the end of the last POST_CHANGE
+ * notification
+ */
+ private ElementTree lastPostChangeTree;
+
+ private ResourceChangeListenerList listeners;
+
+ protected boolean notificationRequested = false;
+ private Job notifyJob;
+ Workspace workspace;
+
+ public NotificationManager(Workspace workspace) {
+ this.workspace = workspace;
+ listeners = new ResourceChangeListenerList();
+ notifyJob = new NotifyJob();
+ }
+
+ public void addListener(IResourceChangeListener listener, int eventMask) {
+ listeners.add(listener, eventMask);
+ if (ResourceStats.TRACE_LISTENERS)
+ ResourceStats.listenerAdded(listener);
+ }
+
+ /**
+ * Indicates the beginning of a block where periodic notifications should be avoided.
+ * Returns true if notification avoidance really started, and false for nested
+ * operations.
+ */
+ public boolean beginAvoidNotify() {
+ return avoidNotify.add(Thread.currentThread());
+ }
+
+ /**
+ * Signals the beginning of the notification phase at the end of a top level operation.
+ */
+ public void beginNotify() {
+ notifyJob.cancel();
+ notificationRequested = false;
+ }
+
+ /**
+ * The main broadcast point for notification deltas
+ */
+ public void broadcastChanges(ElementTree lastState, ResourceChangeEvent event, boolean lockTree) {
+ final int type = event.getType();
+ try {
+ // Do the notification if there are listeners for events of the given type.
+ if (!listeners.hasListenerFor(type))
+ return;
+ isNotifying = true;
+ ResourceDelta delta = getDelta(lastState, type);
+ //don't broadcast POST_CHANGE or autobuild events if the delta is empty
+ if (delta == null || delta.getKind() == 0) {
+ int trigger = event.getBuildKind();
+ if (trigger == IncrementalProjectBuilder.AUTO_BUILD || trigger == 0)
+ return;
+ }
+ event.setDelta(delta);
+ long start = System.currentTimeMillis();
+ notify(getListeners(), event, lockTree);
+ lastNotifyDuration = System.currentTimeMillis() - start;
+ } finally {
+ // Update the state regardless of whether people are listening.
+ isNotifying = false;
+ cleanUp(lastState, type);
+ }
+ }
+
+ /**
+ * Performs cleanup at the end of a resource change notification
+ */
+ private void cleanUp(ElementTree lastState, int type) {
+ // Remember the current state as the last notified state if requested.
+ // Be sure to clear out the old delta
+ boolean postChange = type == IResourceChangeEvent.POST_CHANGE;
+ if (postChange || type == IResourceChangeEvent.POST_BUILD) {
+ long id = workspace.getMarkerManager().getChangeId();
+ lastState.immutable();
+ if (postChange) {
+ lastPostChangeTree = lastState;
+ lastPostChangeId = id;
+ } else {
+ lastPostBuildTree = lastState;
+ lastPostBuildId = id;
+ }
+ workspace.getMarkerManager().resetMarkerDeltas(Math.min(lastPostBuildId, lastPostChangeId));
+ lastDelta = null;
+ lastDeltaState = lastState;
+ }
+ }
+
+ /**
+ * Helper method for the save participant lifecycle computation. */
+ public void broadcastChanges(IResourceChangeListener listener, int type, IResourceDelta delta) {
+ ResourceChangeListenerList.ListenerEntry[] entries;
+ entries = new ResourceChangeListenerList.ListenerEntry[] {new ResourceChangeListenerList.ListenerEntry(listener, type)};
+ notify(entries, new ResourceChangeEvent(workspace, type, 0, delta), false);
+ }
+
+ /**
+ * Indicates the end of a block where periodic notifications should be avoided.
+ */
+ public void endAvoidNotify() {
+ avoidNotify.remove(Thread.currentThread());
+ }
+
+ /**
+ * Requests that a periodic notification be scheduled
+ */
+ public void requestNotify() {
+ //don't do intermediate notifications if the current thread doesn't want them
+ if (isNotifying || avoidNotify.contains(Thread.currentThread()))
+ return;
+ //notifications must never take more than one tenth of operation time
+ long delay = Math.max(NOTIFICATION_DELAY, lastNotifyDuration * 10);
+ if (notifyJob.getState() == Job.NONE)
+ notifyJob.schedule(delay);
+ }
+
+ /**
+ * Computes and returns the resource delta for the given event type and the
+ * given current tree state.
+ */
+ protected ResourceDelta getDelta(ElementTree tree, int type) {
+ long id = workspace.getMarkerManager().getChangeId();
+ // If we have a delta from last time and no resources have changed
+ // since then, we can reuse the delta structure.
+ // However, be sure not to mix deltas from post_change with build events, because they use
+ // a different reference point for delta computation.
+ boolean postChange = type == IResourceChangeEvent.POST_CHANGE;
+ if (!postChange && lastDelta != null && !ElementTree.hasChanges(tree, lastDeltaState, ResourceComparator.getNotificationComparator(), true)) {
+ // Markers may have changed since the delta was generated. If so, get the new
+ // marker state and insert it in to the delta which is being reused.
+ if (id != lastDeltaId) {
+ Map markerDeltas = workspace.getMarkerManager().getMarkerDeltas(lastPostBuildId);
+ lastDelta.updateMarkers(markerDeltas);
+ }
+ } else {
+ // We don't have a delta or something changed so recompute the whole deal.
+ ElementTree oldTree = postChange ? lastPostChangeTree : lastPostBuildTree;
+ long markerId = postChange ? lastPostChangeId : lastPostBuildId;
+ lastDelta = ResourceDeltaFactory.computeDelta(workspace, oldTree, tree, Path.ROOT, markerId + 1);
+ }
+ // remember the state of the world when this delta was consistent
+ lastDeltaState = tree;
+ lastDeltaId = id;
+ return lastDelta;
+ }
+
+ protected ResourceChangeListenerList.ListenerEntry[] getListeners() {
+ return listeners.getListeners();
+ }
+
+ @Override
+ public void handleEvent(LifecycleEvent event) {
+ switch (event.kind) {
+ case LifecycleEvent.PRE_PROJECT_CLOSE :
+ if (!listeners.hasListenerFor(IResourceChangeEvent.PRE_CLOSE))
+ return;
+ IProject project = (IProject) event.resource;
+ notify(getListeners(), new ResourceChangeEvent(workspace, IResourceChangeEvent.PRE_CLOSE, project), true);
+ break;
+ case LifecycleEvent.PRE_PROJECT_MOVE :
+ //only notify deletion on move if old project handle is going
+ // away
+ if (event.resource.equals(event.newResource))
+ return;
+ //fall through
+ case LifecycleEvent.PRE_PROJECT_DELETE :
+ if (!listeners.hasListenerFor(IResourceChangeEvent.PRE_DELETE))
+ return;
+ project = (IProject) event.resource;
+ notify(getListeners(), new ResourceChangeEvent(workspace, IResourceChangeEvent.PRE_DELETE, project), true);
+ break;
+ case LifecycleEvent.PRE_REFRESH :
+ if (!listeners.hasListenerFor(IResourceChangeEvent.PRE_REFRESH))
+ return;
+ if (event.resource.getType() == IResource.PROJECT)
+ notify(getListeners(), new ResourceChangeEvent(event.resource, IResourceChangeEvent.PRE_REFRESH, event.resource), true);
+ else if (event.resource.getType() == IResource.ROOT)
+ notify(getListeners(), new ResourceChangeEvent(workspace, IResourceChangeEvent.PRE_REFRESH, null), true);
+ break;
+ }
+ }
+
+ private void notify(ResourceChangeListenerList.ListenerEntry[] resourceListeners, final ResourceChangeEvent event, final boolean lockTree) {
+ int type = event.getType();
+ boolean oldLock = workspace.isTreeLocked();
+ if (lockTree)
+ workspace.setTreeLocked(true);
+ try {
+ for (int i = 0; i < resourceListeners.length; i++) {
+ if ((type & resourceListeners[i].eventMask) != 0) {
+ final IResourceChangeListener listener = resourceListeners[i].listener;
+ if (ResourceStats.TRACE_LISTENERS)
+ ResourceStats.startNotify(listener);
+ SafeRunner.run(new ISafeRunnable() {
+ @Override
+ public void handleException(Throwable e) {
+ // exception logged in SafeRunner#run
+ }
+
+ @Override
+ public void run() throws Exception {
+ if (Policy.DEBUG_NOTIFICATIONS)
+ Policy.debug("Notifying " + listener.getClass().getName() + " about resource change event" + event.toDebugString()); //$NON-NLS-1$ //$NON-NLS-2$
+ listener.resourceChanged(event);
+ }
+ });
+ if (ResourceStats.TRACE_LISTENERS)
+ ResourceStats.endNotify();
+ }
+ }
+ } finally {
+ if (lockTree)
+ workspace.setTreeLocked(oldLock);
+ }
+ }
+
+ public void removeListener(IResourceChangeListener listener) {
+ listeners.remove(listener);
+ if (ResourceStats.TRACE_LISTENERS)
+ ResourceStats.listenerRemoved(listener);
+ }
+
+ /**
+ * Returns true if a notification is needed. This happens if
+ * sufficient time has elapsed since the last notification
+ * @return true if a notification is needed, and false otherwise
+ */
+ public boolean shouldNotify() {
+ return !isNotifying && notificationRequested;
+ }
+
+ @Override
+ public void shutdown(IProgressMonitor monitor) {
+ //wipe out any existing listeners
+ listeners = new ResourceChangeListenerList();
+ }
+
+ @Override
+ public void startup(IProgressMonitor monitor) {
+ // get the current state of the workspace as the starting point and
+ // tell the workspace to track changes from there. This gives the
+ // notification manager an initial basis for comparison.
+ lastPostBuildTree = lastPostChangeTree = workspace.getElementTree();
+ workspace.addLifecycleListener(this);
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/PathVariableChangeEvent.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/PathVariableChangeEvent.java
new file mode 100644
index 0000000000..59b7df95f3
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/PathVariableChangeEvent.java
@@ -0,0 +1,93 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2015 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.events;
+
+import java.util.EventObject;
+import org.eclipse.core.resources.IPathVariableChangeEvent;
+import org.eclipse.core.resources.IPathVariableManager;
+import org.eclipse.core.runtime.IPath;
+
+/**
+ * Describes a change in path variable. Core's default implementation for the
+ * IPathVariableChangeEvent interface.
+ */
+public class PathVariableChangeEvent extends EventObject implements IPathVariableChangeEvent {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * The name of the changed variable.
+ */
+ private String variableName;
+
+ /**
+ * The value of the changed variable (may be null).
+ */
+ private IPath value;
+
+ /** The event type. */
+ private int type;
+
+ /**
+ * Constructor for this class.
+ */
+ public PathVariableChangeEvent(IPathVariableManager source, String variableName, IPath value, int type) {
+ super(source);
+ if (type < VARIABLE_CHANGED || type > VARIABLE_DELETED)
+ throw new IllegalArgumentException("Invalid event type: " + type); //$NON-NLS-1$
+ this.variableName = variableName;
+ this.value = value;
+ this.type = type;
+ }
+
+ /**
+ * @see org.eclipse.core.resources.IPathVariableChangeEvent#getValue()
+ */
+ @Override
+ public IPath getValue() {
+ return value;
+ }
+
+ /**
+ * @see org.eclipse.core.resources.IPathVariableChangeEvent#getVariableName()
+ */
+ @Override
+ public String getVariableName() {
+ return variableName;
+ }
+
+ /**
+ * @see org.eclipse.core.resources.IPathVariableChangeEvent#getType()
+ */
+ @Override
+ public int getType() {
+ return type;
+ }
+
+ /**
+ * Return a string representation of this object.
+ */
+ @Override
+ public String toString() {
+ String[] typeStrings = {"VARIABLE_CHANGED", "VARIABLE_CREATED", "VARIABLE_DELETED"}; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ StringBuffer sb = new StringBuffer(getClass().getName());
+ sb.append("[variable = "); //$NON-NLS-1$
+ sb.append(variableName);
+ sb.append(", type = "); //$NON-NLS-1$
+ sb.append(typeStrings[type - 1]);
+ if (type != VARIABLE_DELETED) {
+ sb.append(", value = "); //$NON-NLS-1$
+ sb.append(value);
+ }
+ sb.append("]"); //$NON-NLS-1$
+ return sb.toString();
+ }
+
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceChangeEvent.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceChangeEvent.java
new file mode 100644
index 0000000000..e2d6eb6fab
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceChangeEvent.java
@@ -0,0 +1,155 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2015 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * James Blackburn (Broadcom Corp.) - ongoing development
+ *******************************************************************************/
+package org.eclipse.core.internal.events;
+
+import java.util.*;
+import org.eclipse.core.internal.resources.*;
+import org.eclipse.core.resources.*;
+import org.eclipse.core.runtime.IPath;
+
+public class ResourceChangeEvent extends EventObject implements IResourceChangeEvent {
+ private static final IMarkerDelta[] NO_MARKER_DELTAS = new IMarkerDelta[0];
+ private static final long serialVersionUID = 1L;
+ IResourceDelta delta;
+ IResource resource;
+
+ /**
+ * The build trigger for this event, or 0 if not applicable.
+ */
+ private int trigger = 0;
+ int type;
+
+ protected ResourceChangeEvent(Object source, int type, IResource resource) {
+ super(source);
+ this.resource = resource;
+ this.type = type;
+ }
+
+ public ResourceChangeEvent(Object source, int type, int buildKind, IResourceDelta delta) {
+ super(source);
+ this.delta = delta;
+ this.trigger = buildKind;
+ this.type = type;
+ }
+
+ /**
+ * @see IResourceChangeEvent#findMarkerDeltas(String, boolean)
+ */
+ @Override
+ public IMarkerDelta[] findMarkerDeltas(String findType, boolean includeSubtypes) {
+ if (delta == null)
+ return NO_MARKER_DELTAS;
+ ResourceDeltaInfo info = ((ResourceDelta) delta).getDeltaInfo();
+ if (info == null)
+ return NO_MARKER_DELTAS;
+ //Map of IPath -> MarkerSet containing MarkerDelta objects
+ Map markerDeltas = info.getMarkerDeltas();
+ if (markerDeltas == null || markerDeltas.size() == 0)
+ return NO_MARKER_DELTAS;
+ ArrayList matching = new ArrayList();
+ Iterator deltaSets = markerDeltas.values().iterator();
+ while (deltaSets.hasNext()) {
+ MarkerSet deltas = deltaSets.next();
+ IMarkerSetElement[] elements = deltas.elements();
+ for (int i = 0; i < elements.length; i++) {
+ MarkerDelta markerDelta = (MarkerDelta) elements[i];
+ //our inclusion test depends on whether we are considering subtypes
+ if (findType == null || (includeSubtypes ? markerDelta.isSubtypeOf(findType) : markerDelta.getType().equals(findType)))
+ matching.add(markerDelta);
+ }
+ }
+ return matching.toArray(new IMarkerDelta[matching.size()]);
+ }
+
+ /**
+ * @see IResourceChangeEvent#getBuildKind()
+ */
+ @Override
+ public int getBuildKind() {
+ return trigger;
+ }
+
+ /**
+ * @see IResourceChangeEvent#getDelta()
+ */
+ @Override
+ public IResourceDelta getDelta() {
+ return delta;
+ }
+
+ /**
+ * @see IResourceChangeEvent#getResource()
+ */
+ @Override
+ public IResource getResource() {
+ return resource;
+ }
+
+ /**
+ * @see IResourceChangeEvent#getType()
+ */
+ @Override
+ public int getType() {
+ return type;
+ }
+
+ public void setDelta(IResourceDelta value) {
+ delta = value;
+ }
+
+ public String toDebugString() {
+ StringBuilder output = new StringBuilder();
+ output.append("\nType: ");//$NON-NLS-1$
+ switch (type) {
+ case POST_CHANGE :
+ output.append("POST_CHANGE"); //$NON-NLS-1$
+ break;
+ case PRE_CLOSE :
+ output.append("PRE_CLOSE"); //$NON-NLS-1$
+ break;
+ case PRE_DELETE :
+ output.append("PRE_DELETE"); //$NON-NLS-1$
+ break;
+ case PRE_BUILD :
+ output.append("PRE_BUILD"); //$NON-NLS-1$
+ break;
+ case POST_BUILD :
+ output.append("POST_BUILD"); //$NON-NLS-1$
+ break;
+ case PRE_REFRESH :
+ output.append("PRE_REFRESH"); //$NON-NLS-1$
+ break;
+ default :
+ output.append("?"); //$NON-NLS-1$
+ break;
+ }
+ output.append("\nBuild kind: "); //$NON-NLS-1$
+ switch (trigger) {
+ case IncrementalProjectBuilder.FULL_BUILD :
+ output.append("FULL_BUILD"); //$NON-NLS-1$
+ break;
+ case IncrementalProjectBuilder.AUTO_BUILD :
+ case IncrementalProjectBuilder.INCREMENTAL_BUILD :
+ output.append("INCREMENTAL_BUILD"); //$NON-NLS-1$
+ break;
+ case IncrementalProjectBuilder.CLEAN_BUILD :
+ output.append("CLEAN_BUILD"); //$NON-NLS-1$
+ break;
+ default :
+ output.append(trigger);
+ break;
+ }
+ output.append("\nResource: " + (resource == null ? "null" : resource)); //$NON-NLS-1$ //$NON-NLS-2$
+ output.append("\nDelta:" + (delta == null ? " null" : ((ResourceDelta) delta).toDeepDebugString())); //$NON-NLS-1$ //$NON-NLS-2$
+ return output.toString();
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceChangeListenerList.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceChangeListenerList.java
new file mode 100644
index 0000000000..2fc06a1f9d
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceChangeListenerList.java
@@ -0,0 +1,180 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2008 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.events;
+
+import org.eclipse.core.resources.IResourceChangeListener;
+import org.eclipse.core.runtime.Assert;
+
+/**
+ * This class is used to maintain a list of listeners. It is a fairly lightweight object,
+ * occupying minimal space when no listeners are registered.
+ *
+ * Note that the add method checks for and eliminates
+ * duplicates based on identity (not equality). Likewise, the
+ * remove method compares based on identity.
+ *
+ *
+ * This implementation is thread safe. The listener list is copied every time
+ * it is modified, so readers do not need to copy or synchronize. This optimizes
+ * for frequent reads and infrequent writes, and assumes that readers can
+ * be trusted not to modify the returned array.
+ */
+public class ResourceChangeListenerList {
+
+ static class ListenerEntry {
+ int eventMask;
+ IResourceChangeListener listener;
+
+ ListenerEntry(IResourceChangeListener listener, int eventMask) {
+ this.listener = listener;
+ this.eventMask = eventMask;
+ }
+ }
+
+ /**
+ * The empty array singleton instance.
+ */
+ private static final ListenerEntry[] EMPTY_ARRAY = new ListenerEntry[0];
+
+ private int count1 = 0;
+ private int count2 = 0;
+ private int count4 = 0;
+ private int count8 = 0;
+ private int count16 = 0;
+ private int count32 = 0;
+
+ /**
+ * The list of listeners. Maintains invariant: listeners != null.
+ */
+ private volatile ListenerEntry[] listeners = EMPTY_ARRAY;
+
+ /**
+ * Adds the given listener to this list. Has no effect if an identical listener
+ * is already registered.
+ *
+ * @param listener the listener
+ * @param mask event types
+ */
+ public synchronized void add(IResourceChangeListener listener, int mask) {
+ Assert.isNotNull(listener);
+ if (mask == 0) {
+ remove(listener);
+ return;
+ }
+ ResourceChangeListenerList.ListenerEntry entry = new ResourceChangeListenerList.ListenerEntry(listener, mask);
+ final int oldSize = listeners.length;
+ // check for duplicates using identity
+ for (int i = 0; i < oldSize; ++i) {
+ if (listeners[i].listener == listener) {
+ removing(listeners[i].eventMask);
+ adding(mask);
+ listeners[i] = entry;
+ return;
+ }
+ }
+ adding(mask);
+ // Thread safety: copy on write to protect concurrent readers.
+ ListenerEntry[] newListeners = new ListenerEntry[oldSize + 1];
+ System.arraycopy(listeners, 0, newListeners, 0, oldSize);
+ newListeners[oldSize] = entry;
+ //atomic assignment
+ this.listeners = newListeners;
+ }
+
+ private void adding(int mask) {
+ if ((mask & 1) != 0)
+ count1++;
+ if ((mask & 2) != 0)
+ count2++;
+ if ((mask & 4) != 0)
+ count4++;
+ if ((mask & 8) != 0)
+ count8++;
+ if ((mask & 16) != 0)
+ count16++;
+ if ((mask & 32) != 0)
+ count32++;
+ }
+
+ /**
+ * Returns an array containing all the registered listeners.
+ * The resulting array is unaffected by subsequent adds or removes.
+ * If there are no listeners registered, the result is an empty array
+ * singleton instance (no garbage is created).
+ * Use this method when notifying listeners, so that any modifications
+ * to the listener list during the notification will have no effect on the
+ * notification itself.
+ *
+ * Note: Clients must not modify the returned list
+ * @return the list of registered listeners that must not be modified
+ */
+ public ListenerEntry[] getListeners() {
+ return listeners;
+ }
+
+ public boolean hasListenerFor(int event) {
+ if (event == 1)
+ return count1 > 0;
+ if (event == 2)
+ return count2 > 0;
+ if (event == 4)
+ return count4 > 0;
+ if (event == 8)
+ return count8 > 0;
+ if (event == 16)
+ return count16 > 0;
+ if (event == 32)
+ return count32 > 0;
+ return false;
+ }
+
+ /**
+ * Removes the given listener from this list. Has no effect if an identical
+ * listener was not already registered.
+ *
+ * @param listener the listener to remove
+ */
+ public synchronized void remove(IResourceChangeListener listener) {
+ Assert.isNotNull(listener);
+ final int oldSize = listeners.length;
+ for (int i = 0; i < oldSize; ++i) {
+ if (listeners[i].listener == listener) {
+ removing(listeners[i].eventMask);
+ if (oldSize == 1) {
+ listeners = EMPTY_ARRAY;
+ } else {
+ // Thread safety: create new array to avoid affecting concurrent readers
+ ListenerEntry[] newListeners = new ListenerEntry[oldSize - 1];
+ System.arraycopy(listeners, 0, newListeners, 0, i);
+ System.arraycopy(listeners, i + 1, newListeners, i, oldSize - i - 1);
+ //atomic assignment to field
+ this.listeners = newListeners;
+ }
+ return;
+ }
+ }
+ }
+
+ private void removing(int mask) {
+ if ((mask & 1) != 0)
+ count1--;
+ if ((mask & 2) != 0)
+ count2--;
+ if ((mask & 4) != 0)
+ count4--;
+ if ((mask & 8) != 0)
+ count8--;
+ if ((mask & 16) != 0)
+ count16--;
+ if ((mask & 32) != 0)
+ count32--;
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceComparator.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceComparator.java
new file mode 100644
index 0000000000..6c4af78dd8
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceComparator.java
@@ -0,0 +1,200 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2015 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.events;
+
+import org.eclipse.core.internal.resources.ICoreConstants;
+import org.eclipse.core.internal.resources.ResourceInfo;
+import org.eclipse.core.internal.watson.IElementComparator;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IResourceDelta;
+
+/**
+ * Compares two Resources and returns flags describing how
+ * they have changed, for use in computing deltas.
+ * Implementation note: rather than defining a partial order
+ * as specified by IComparator, the compare operation returns
+ * a set of flags instead. The delta computation only cares
+ * whether the comparison is zero (equal) or non-zero (not equal).
+ */
+public class ResourceComparator implements IElementComparator, ICoreConstants {
+ /* Singleton instances */
+ protected static final ResourceComparator notificationSingleton = new ResourceComparator(true, false);
+ protected static final ResourceComparator buildSingleton = new ResourceComparator(false, false);
+
+ /**
+ * Boolean indicating whether or not this comparator is to be used for
+ * a notification. (as opposed to a build) Notifications include extra information
+ * like marker and sync info changes.
+ */
+ private boolean notification;
+
+ /**
+ * Boolean indicating whether or not this comparator is to be used for
+ * snapshot. Snapshots care about extra information such as the used bit.
+ */
+ private boolean save;
+
+ /**
+ * Returns a comparator which compares resource infos, suitable for computing
+ * save and snapshot deltas.
+ */
+ public static ResourceComparator getSaveComparator() {
+ return new ResourceComparator(false, true);
+ }
+
+ /**
+ * Returns a comparator which compares resource infos, suitable for computing
+ * build deltas.
+ */
+ public static ResourceComparator getBuildComparator() {
+ return buildSingleton;
+ }
+
+ /**
+ * Returns a comparator which compares resource infos, suitable for computing
+ * build deltas.
+ */
+ public static ResourceComparator getNotificationComparator() {
+ return notificationSingleton;
+ }
+
+ /**
+ * Create a comparator which compares resource infos.
+ * @param notification if true, check for marker deltas.
+ * @param save if true, check for all resource changes that snapshot needs
+ */
+ private ResourceComparator(boolean notification, boolean save) {
+ this.notification = notification;
+ this.save = save;
+ }
+
+ /**
+ * Compare the ElementInfos for two resources.
+ */
+ @Override
+ public int compare(Object o1, Object o2) {
+ // == handles null, null.
+ if (o1 == o2)
+ return IResourceDelta.NO_CHANGE;
+ int result = 0;
+ if (o1 == null)
+ return ((ResourceInfo) o2).isSet(M_PHANTOM) ? IResourceDelta.ADDED_PHANTOM : IResourceDelta.ADDED;
+ if (o2 == null)
+ return ((ResourceInfo) o1).isSet(M_PHANTOM) ? IResourceDelta.REMOVED_PHANTOM : IResourceDelta.REMOVED;
+ if (!(o1 instanceof ResourceInfo && o2 instanceof ResourceInfo))
+ return IResourceDelta.NO_CHANGE;
+ ResourceInfo oldElement = (ResourceInfo) o1;
+ ResourceInfo newElement = (ResourceInfo) o2;
+ if (!oldElement.isSet(M_PHANTOM) && newElement.isSet(M_PHANTOM))
+ return IResourceDelta.REMOVED;
+ if (oldElement.isSet(M_PHANTOM) && !newElement.isSet(M_PHANTOM))
+ return IResourceDelta.ADDED;
+ if (!compareOpen(oldElement, newElement))
+ result |= IResourceDelta.OPEN;
+ if (!compareContents(oldElement, newElement)) {
+ if (oldElement.getType() == IResource.PROJECT)
+ result |= IResourceDelta.DESCRIPTION;
+ else if (newElement.getType() == IResource.FILE || oldElement.getType() == IResource.FILE)
+ result |= IResourceDelta.CONTENT;
+ }
+ if (!compareType(oldElement, newElement))
+ result |= IResourceDelta.TYPE;
+ if (!compareNodeIDs(oldElement, newElement)) {
+ result |= IResourceDelta.REPLACED;
+ // if the node was replaced and the old and new were files, this is also a content change.
+ if (oldElement.getType() == IResource.FILE && newElement.getType() == IResource.FILE)
+ result |= IResourceDelta.CONTENT;
+ }
+ if (compareLocal(oldElement, newElement))
+ result |= IResourceDelta.LOCAL_CHANGED;
+ if (!compareCharsets(oldElement, newElement))
+ result |= IResourceDelta.ENCODING;
+ if (!compareDerived(oldElement, newElement))
+ result |= IResourceDelta.DERIVED_CHANGED;
+ if (notification && !compareSync(oldElement, newElement))
+ result |= IResourceDelta.SYNC;
+ if (notification && !compareMarkers(oldElement, newElement))
+ result |= IResourceDelta.MARKERS;
+ if (save && !compareUsed(oldElement, newElement))
+ result |= IResourceDelta.CHANGED;
+ return result == 0 ? 0 : result | IResourceDelta.CHANGED;
+ }
+
+ private boolean compareDerived(ResourceInfo oldElement, ResourceInfo newElement) {
+ return oldElement.isSet(ICoreConstants.M_DERIVED) == newElement.isSet(ICoreConstants.M_DERIVED);
+ }
+
+ private boolean compareCharsets(ResourceInfo oldElement, ResourceInfo newElement) {
+ return oldElement.getCharsetGenerationCount() == newElement.getCharsetGenerationCount();
+ }
+
+ /**
+ * Compares the contents of the ResourceInfo.
+ */
+ private boolean compareContents(ResourceInfo oldElement, ResourceInfo newElement) {
+ return oldElement.getContentId() == newElement.getContentId();
+ }
+
+ /**
+ * Compares the existence of local files/folders for two linked resources.
+ */
+ private boolean compareLocal(ResourceInfo oldElement, ResourceInfo newElement) {
+ //only applicable for linked resources
+ if (!oldElement.isSet(ICoreConstants.M_LINK) || !newElement.isSet(ICoreConstants.M_LINK))
+ return false;
+ long oldStamp = oldElement.getModificationStamp();
+ long newStamp = newElement.getModificationStamp();
+ return (oldStamp == -1 || newStamp == -1) && (oldStamp != newStamp);
+ }
+
+ private boolean compareMarkers(ResourceInfo oldElement, ResourceInfo newElement) {
+ // If both sets of markers are null then perhaps we added some markers
+ // but then deleted them right away before notification. In that case
+ // don't signify a marker change in the delta.
+ boolean bothNull = oldElement.getMarkers(false) == null && newElement.getMarkers(false) == null;
+ return bothNull || oldElement.getMarkerGenerationCount() == newElement.getMarkerGenerationCount();
+ }
+
+ /**
+ * Compares the node IDs of the ElementInfos for two resources.
+ */
+ private boolean compareNodeIDs(ResourceInfo oldElement, ResourceInfo newElement) {
+ return oldElement.getNodeId() == newElement.getNodeId();
+ }
+
+ /**
+ * Compares the open state of the ElementInfos for two resources.
+ */
+ private boolean compareOpen(ResourceInfo oldElement, ResourceInfo newElement) {
+ return oldElement.isSet(M_OPEN) == newElement.isSet(M_OPEN);
+ }
+
+ /**
+ * Compares the sync state for two resources.
+ */
+ private boolean compareSync(ResourceInfo oldElement, ResourceInfo newElement) {
+ return oldElement.getSyncInfoGenerationCount() == newElement.getSyncInfoGenerationCount();
+ }
+
+ /**
+ * Compares the type of the ResourceInfo.
+ */
+ private boolean compareType(ResourceInfo oldElement, ResourceInfo newElement) {
+ return oldElement.getType() == newElement.getType();
+ }
+
+ /**
+ * Compares the used state of the ElementInfos for two resources.
+ */
+ private boolean compareUsed(ResourceInfo oldElement, ResourceInfo newElement) {
+ return oldElement.isSet(M_USED) == newElement.isSet(M_USED);
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceDelta.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceDelta.java
new file mode 100644
index 0000000000..4c9b7c0045
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceDelta.java
@@ -0,0 +1,550 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2015 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * James Blackburn (Broadcom Corp.) - ongoing development
+ *******************************************************************************/
+package org.eclipse.core.internal.events;
+
+import java.util.Iterator;
+import java.util.Map;
+import org.eclipse.core.internal.resources.*;
+import org.eclipse.core.internal.watson.ElementTree;
+import org.eclipse.core.resources.*;
+import org.eclipse.core.runtime.*;
+
+/**
+ * Concrete implementation of the IResourceDelta interface. Each ResourceDelta
+ * object represents changes that have occurred between two states of the
+ * resource tree.
+ */
+public class ResourceDelta extends PlatformObject implements IResourceDelta {
+ protected IPath path;
+ protected ResourceDeltaInfo deltaInfo;
+ protected int status;
+ protected ResourceInfo oldInfo;
+ protected ResourceInfo newInfo;
+ protected ResourceDelta[] children;
+ // don't aggressively set this, but cache it if called once
+ protected IResource cachedResource;
+
+ //
+ protected static int KIND_MASK = 0xFF;
+ private static IMarkerDelta[] EMPTY_MARKER_DELTAS = new IMarkerDelta[0];
+
+ protected ResourceDelta(IPath path, ResourceDeltaInfo deltaInfo) {
+ this.path = path;
+ this.deltaInfo = deltaInfo;
+ }
+
+ @Override
+ public void accept(IResourceDeltaVisitor visitor) throws CoreException {
+ accept(visitor, 0);
+ }
+
+ @Override
+ public void accept(IResourceDeltaVisitor visitor, boolean includePhantoms) throws CoreException {
+ accept(visitor, includePhantoms ? IContainer.INCLUDE_PHANTOMS : 0);
+ }
+
+ @Override
+ public void accept(IResourceDeltaVisitor visitor, int memberFlags) throws CoreException {
+ final boolean includePhantoms = (memberFlags & IContainer.INCLUDE_PHANTOMS) != 0;
+ final boolean includeTeamPrivate = (memberFlags & IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS) != 0;
+ final boolean includeHidden = (memberFlags & IContainer.INCLUDE_HIDDEN) != 0;
+ int mask = includePhantoms ? ALL_WITH_PHANTOMS : REMOVED | ADDED | CHANGED;
+ if ((getKind() & mask) == 0)
+ return;
+ if (!visitor.visit(this))
+ return;
+ for (int i = 0; i < children.length; i++) {
+ ResourceDelta childDelta = children[i];
+ // quietly exclude team-private, hidden and phantom members unless explicitly included
+ if (!includeTeamPrivate && childDelta.isTeamPrivate())
+ continue;
+ if (!includePhantoms && childDelta.isPhantom())
+ continue;
+ if (!includeHidden && childDelta.isHidden())
+ continue;
+ childDelta.accept(visitor, memberFlags);
+ }
+ }
+
+ /**
+ * Check for marker deltas, and set the appropriate change flag if there are any.
+ */
+ protected void checkForMarkerDeltas() {
+ if (deltaInfo.getMarkerDeltas() == null)
+ return;
+ int kind = getKind();
+ // Only need to check for added and removed, or for changes on the workspace.
+ // For changed, the bit is set in the comparator.
+ if (path.isRoot() || kind == ADDED || kind == REMOVED) {
+ MarkerSet changes = deltaInfo.getMarkerDeltas().get(path);
+ if (changes != null && changes.size() > 0) {
+ status |= MARKERS;
+ // If there have been marker changes, then ensure kind is CHANGED (if not ADDED or REMOVED).
+ // See 1FV9K20: ITPUI:WINNT - severe - task list - add or delete not working
+ if (kind == 0)
+ status |= CHANGED;
+ }
+ }
+ }
+
+ @Override
+ public IResourceDelta findMember(IPath path) {
+ int segmentCount = path.segmentCount();
+ if (segmentCount == 0)
+ return this;
+
+ //iterate over the path and find matching child delta
+ ResourceDelta current = this;
+ segments: for (int i = 0; i < segmentCount; i++) {
+ IResourceDelta[] currentChildren = current.children;
+ for (int j = 0, jmax = currentChildren.length; j < jmax; j++) {
+ if (currentChildren[j].getFullPath().lastSegment().equals(path.segment(i))) {
+ current = (ResourceDelta) currentChildren[j];
+ continue segments;
+ }
+ }
+ //matching child not found, return
+ return null;
+ }
+ return current;
+ }
+
+ /**
+ * Delta information on moves and on marker deltas can only be computed after
+ * the delta has been built. This method fixes up the delta to accurately
+ * reflect moves (setting MOVED_FROM and MOVED_TO), and marker changes on
+ * added and removed resources.
+ */
+ protected void fixMovesAndMarkers(ElementTree oldTree) {
+ NodeIDMap nodeIDMap = deltaInfo.getNodeIDMap();
+ if (!path.isRoot() && !nodeIDMap.isEmpty()) {
+ int kind = getKind();
+ switch (kind) {
+ case CHANGED :
+ case ADDED :
+ IPath oldPath = nodeIDMap.getOldPath(newInfo.getNodeId());
+ if (oldPath != null && !oldPath.equals(path)) {
+ //get the old info from the old tree
+ ResourceInfo actualOldInfo = (ResourceInfo) oldTree.getElementData(oldPath);
+ // Replace change flags by comparing old info with new info,
+ // Note that we want to retain the kind flag, but replace all other flags
+ // This is done only for MOVED_FROM, not MOVED_TO, since a resource may be both.
+ status = (status & KIND_MASK) | (deltaInfo.getComparator().compare(actualOldInfo, newInfo) & ~KIND_MASK);
+ status |= MOVED_FROM;
+ //our API states that MOVED_FROM must be in conjunction with ADDED | (CHANGED + REPLACED)
+ if (kind == CHANGED)
+ status = status | REPLACED | CONTENT;
+ //check for gender change
+ if (oldInfo != null && newInfo != null && oldInfo.getType() != newInfo.getType())
+ status |= TYPE;
+ }
+ }
+ switch (kind) {
+ case REMOVED :
+ case CHANGED :
+ IPath newPath = nodeIDMap.getNewPath(oldInfo.getNodeId());
+ if (newPath != null && !newPath.equals(path)) {
+ status |= MOVED_TO;
+ //our API states that MOVED_TO must be in conjunction with REMOVED | (CHANGED + REPLACED)
+ if (kind == CHANGED)
+ status = status | REPLACED | CONTENT;
+ }
+ }
+ }
+
+ //check for marker deltas -- this is affected by move computation
+ //so must happen afterwards
+ checkForMarkerDeltas();
+
+ //recurse on children
+ for (int i = 0; i < children.length; i++)
+ children[i].fixMovesAndMarkers(oldTree);
+ }
+
+ @Override
+ public IResourceDelta[] getAffectedChildren() {
+ return getAffectedChildren(ADDED | REMOVED | CHANGED, IResource.NONE);
+ }
+
+ @Override
+ public IResourceDelta[] getAffectedChildren(int kindMask) {
+ return getAffectedChildren(kindMask, IResource.NONE);
+ }
+
+ @Override
+ public IResourceDelta[] getAffectedChildren(int kindMask, int memberFlags) {
+ int numChildren = children.length;
+ //if there are no children, they all match
+ if (numChildren == 0)
+ return children;
+ boolean includePhantoms = (memberFlags & IContainer.INCLUDE_PHANTOMS) != 0;
+ boolean includeTeamPrivate = (memberFlags & IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS) != 0;
+ boolean includeHidden = (memberFlags & IContainer.INCLUDE_HIDDEN) != 0;
+ // reduce INCLUDE_PHANTOMS member flag to kind mask
+ if (includePhantoms)
+ kindMask |= ADDED_PHANTOM | REMOVED_PHANTOM;
+
+ //first count the number of matches so we can allocate the exact array size
+ int matching = 0;
+ for (int i = 0; i < numChildren; i++) {
+ if ((children[i].getKind() & kindMask) == 0)
+ continue;// child has wrong kind
+ if (!includePhantoms && children[i].isPhantom())
+ continue;
+ if (!includeTeamPrivate && children[i].isTeamPrivate())
+ continue; // child has is a team-private member which are not included
+ if (!includeHidden && children[i].isHidden())
+ continue;
+ matching++;
+ }
+ //use arraycopy if all match
+ if (matching == numChildren) {
+ IResourceDelta[] result = new IResourceDelta[children.length];
+ System.arraycopy(children, 0, result, 0, children.length);
+ return result;
+ }
+ //create the appropriate sized array and fill it
+ IResourceDelta[] result = new IResourceDelta[matching];
+ int nextPosition = 0;
+ for (int i = 0; i < numChildren; i++) {
+ if ((children[i].getKind() & kindMask) == 0)
+ continue; // child has wrong kind
+ if (!includePhantoms && children[i].isPhantom())
+ continue;
+ if (!includeTeamPrivate && children[i].isTeamPrivate())
+ continue; // child has is a team-private member which are not included
+ if (!includeHidden && children[i].isHidden())
+ continue;
+ result[nextPosition++] = children[i];
+ }
+ return result;
+ }
+
+ protected ResourceDeltaInfo getDeltaInfo() {
+ return deltaInfo;
+ }
+
+ @Override
+ public int getFlags() {
+ return status & ~KIND_MASK;
+ }
+
+ @Override
+ public IPath getFullPath() {
+ return path;
+ }
+
+ @Override
+ public int getKind() {
+ return status & KIND_MASK;
+ }
+
+ @Override
+ public IMarkerDelta[] getMarkerDeltas() {
+ Map markerDeltas = deltaInfo.getMarkerDeltas();
+ if (markerDeltas == null)
+ return EMPTY_MARKER_DELTAS;
+ if (path == null)
+ path = Path.ROOT;
+ MarkerSet changes = markerDeltas.get(path);
+ if (changes == null)
+ return EMPTY_MARKER_DELTAS;
+ IMarkerSetElement[] elements = changes.elements();
+ IMarkerDelta[] result = new IMarkerDelta[elements.length];
+ for (int i = 0; i < elements.length; i++)
+ result[i] = (IMarkerDelta) elements[i];
+ return result;
+ }
+
+ @Override
+ public IPath getMovedFromPath() {
+ if ((status & MOVED_FROM) != 0) {
+ return deltaInfo.getNodeIDMap().getOldPath(newInfo.getNodeId());
+ }
+ return null;
+ }
+
+ @Override
+ public IPath getMovedToPath() {
+ if ((status & MOVED_TO) != 0) {
+ return deltaInfo.getNodeIDMap().getNewPath(oldInfo.getNodeId());
+ }
+ return null;
+ }
+
+ @Override
+ public IPath getProjectRelativePath() {
+ IPath full = getFullPath();
+ int count = full.segmentCount();
+ if (count < 0)
+ return null;
+ if (count <= 1) // 0 or 1
+ return Path.EMPTY;
+ return full.removeFirstSegments(1);
+ }
+
+ @Override
+ public IResource getResource() {
+ // return a cached copy if we have one
+ if (cachedResource != null)
+ return cachedResource;
+
+ // if this is a delta for the root then return the root resource
+ if (path.segmentCount() == 0)
+ return deltaInfo.getWorkspace().getRoot();
+ // if the delta is a remove then we have to look for the old info to find the type
+ // of resource to create.
+ ResourceInfo info = null;
+ if ((getKind() & (REMOVED | REMOVED_PHANTOM)) != 0)
+ info = oldInfo;
+ else
+ info = newInfo;
+ if (info == null)
+ Assert.isNotNull(null, "Do not have resource info for resource in delta: " + path); //$NON-NLS-1$
+ cachedResource = deltaInfo.getWorkspace().newResource(path, info.getType());
+ return cachedResource;
+ }
+
+ /**
+ * Returns true if this delta represents a phantom member, and false
+ * otherwise.
+ */
+ protected boolean isPhantom() {
+ //use old info for removals, and new info for added or changed
+ if ((status & (REMOVED | REMOVED_PHANTOM)) != 0)
+ return ResourceInfo.isSet(oldInfo.getFlags(), ICoreConstants.M_PHANTOM);
+ return ResourceInfo.isSet(newInfo.getFlags(), ICoreConstants.M_PHANTOM);
+ }
+
+ /**
+ * Returns true if this delta represents a team private member, and false
+ * otherwise.
+ */
+ protected boolean isTeamPrivate() {
+ //use old info for removals, and new info for added or changed
+ if ((status & (REMOVED | REMOVED_PHANTOM)) != 0)
+ return ResourceInfo.isSet(oldInfo.getFlags(), ICoreConstants.M_TEAM_PRIVATE_MEMBER);
+ return ResourceInfo.isSet(newInfo.getFlags(), ICoreConstants.M_TEAM_PRIVATE_MEMBER);
+ }
+
+ /**
+ * Returns true if this delta represents a hidden member, and false
+ * otherwise.
+ */
+ protected boolean isHidden() {
+ //use old info for removals, and new info for added or changed
+ if ((status & (REMOVED | REMOVED_PHANTOM)) != 0)
+ return ResourceInfo.isSet(oldInfo.getFlags(), ICoreConstants.M_HIDDEN);
+ return ResourceInfo.isSet(newInfo.getFlags(), ICoreConstants.M_HIDDEN);
+ }
+
+ protected void setChildren(ResourceDelta[] children) {
+ this.children = children;
+ }
+
+ protected void setNewInfo(ResourceInfo newInfo) {
+ this.newInfo = newInfo;
+ }
+
+ protected void setOldInfo(ResourceInfo oldInfo) {
+ this.oldInfo = oldInfo;
+ }
+
+ protected void setStatus(int status) {
+ this.status = status;
+ }
+
+ /**
+ * Returns a string representation of this delta's
+ * immediate structure suitable for debug purposes.
+ */
+ public String toDebugString() {
+ final StringBuffer buffer = new StringBuffer();
+ writeDebugString(buffer);
+ return buffer.toString();
+ }
+
+ /**
+ * Returns a string representation of this delta's
+ * deep structure suitable for debug purposes.
+ */
+ public String toDeepDebugString() {
+ final StringBuffer buffer = new StringBuffer("\n"); //$NON-NLS-1$
+ writeDebugString(buffer);
+ for (int i = 0; i < children.length; ++i)
+ buffer.append(children[i].toDeepDebugString());
+ return buffer.toString();
+ }
+
+ /**
+ * For debugging only
+ */
+ @Override
+ public String toString() {
+ return "ResourceDelta(" + path + ')'; //$NON-NLS-1$
+ }
+
+ /**
+ * Provides a new set of markers for the delta. This is used
+ * when the delta is reused in cases where the only changes
+ * are marker changes.
+ */
+ public void updateMarkers(Map markers) {
+ deltaInfo.setMarkerDeltas(markers);
+ }
+
+ /**
+ * Writes a string representation of this delta's
+ * immediate structure on the given string buffer.
+ */
+ public void writeDebugString(StringBuffer buffer) {
+ buffer.append(getFullPath());
+ buffer.append('[');
+ switch (getKind()) {
+ case ADDED :
+ buffer.append('+');
+ break;
+ case ADDED_PHANTOM :
+ buffer.append('>');
+ break;
+ case REMOVED :
+ buffer.append('-');
+ break;
+ case REMOVED_PHANTOM :
+ buffer.append('<');
+ break;
+ case CHANGED :
+ buffer.append('*');
+ break;
+ case NO_CHANGE :
+ buffer.append('~');
+ break;
+ default :
+ buffer.append('?');
+ break;
+ }
+ buffer.append("]: {"); //$NON-NLS-1$
+ int changeFlags = getFlags();
+ boolean prev = false;
+ if ((changeFlags & CONTENT) != 0) {
+ if (prev)
+ buffer.append(" | "); //$NON-NLS-1$
+ buffer.append("CONTENT"); //$NON-NLS-1$
+ prev = true;
+ }
+ if ((changeFlags & LOCAL_CHANGED) != 0) {
+ if (prev)
+ buffer.append(" | "); //$NON-NLS-1$
+ buffer.append("LOCAL_CHANGED"); //$NON-NLS-1$
+ prev = true;
+ }
+ if ((changeFlags & MOVED_FROM) != 0) {
+ if (prev)
+ buffer.append(" | "); //$NON-NLS-1$
+ buffer.append("MOVED_FROM(" + getMovedFromPath() + ")"); //$NON-NLS-1$ //$NON-NLS-2$
+ prev = true;
+ }
+ if ((changeFlags & MOVED_TO) != 0) {
+ if (prev)
+ buffer.append(" | "); //$NON-NLS-1$
+ buffer.append("MOVED_TO(" + getMovedToPath() + ")"); //$NON-NLS-1$ //$NON-NLS-2$
+ prev = true;
+ }
+ if ((changeFlags & OPEN) != 0) {
+ if (prev)
+ buffer.append(" | "); //$NON-NLS-1$
+ buffer.append("OPEN"); //$NON-NLS-1$
+ prev = true;
+ }
+ if ((changeFlags & TYPE) != 0) {
+ if (prev)
+ buffer.append(" | "); //$NON-NLS-1$
+ buffer.append("TYPE"); //$NON-NLS-1$
+ prev = true;
+ }
+ if ((changeFlags & SYNC) != 0) {
+ if (prev)
+ buffer.append(" | "); //$NON-NLS-1$
+ buffer.append("SYNC"); //$NON-NLS-1$
+ prev = true;
+ }
+ if ((changeFlags & MARKERS) != 0) {
+ if (prev)
+ buffer.append(" | "); //$NON-NLS-1$
+ buffer.append("MARKERS"); //$NON-NLS-1$
+ writeMarkerDebugString(buffer);
+ prev = true;
+ }
+ if ((changeFlags & REPLACED) != 0) {
+ if (prev)
+ buffer.append(" | "); //$NON-NLS-1$
+ buffer.append("REPLACED"); //$NON-NLS-1$
+ prev = true;
+ }
+ if ((changeFlags & DESCRIPTION) != 0) {
+ if (prev)
+ buffer.append(" | "); //$NON-NLS-1$
+ buffer.append("DESCRIPTION"); //$NON-NLS-1$
+ prev = true;
+ }
+ if ((changeFlags & ENCODING) != 0) {
+ if (prev)
+ buffer.append(" | "); //$NON-NLS-1$
+ buffer.append("ENCODING"); //$NON-NLS-1$
+ prev = true;
+ }
+ if ((changeFlags & DERIVED_CHANGED) != 0) {
+ if (prev)
+ buffer.append(" | "); //$NON-NLS-1$
+ buffer.append("DERIVED_CHANGED"); //$NON-NLS-1$
+ prev = true;
+ }
+ buffer.append("}"); //$NON-NLS-1$
+ if (isTeamPrivate())
+ buffer.append(" (team private)"); //$NON-NLS-1$
+ if (isHidden())
+ buffer.append(" (hidden)"); //$NON-NLS-1$
+ }
+
+ public void writeMarkerDebugString(StringBuffer buffer) {
+ Map markerDeltas = deltaInfo.getMarkerDeltas();
+ if (markerDeltas == null || markerDeltas.isEmpty())
+ return;
+ buffer.append('[');
+ for (Iterator e = markerDeltas.keySet().iterator(); e.hasNext();) {
+ IPath key = e.next();
+ if (getResource().getFullPath().equals(key)) {
+ IMarkerSetElement[] deltas = markerDeltas.get(key).elements();
+ boolean addComma = false;
+ for (int i = 0; i < deltas.length; i++) {
+ IMarkerDelta delta = (IMarkerDelta) deltas[i];
+ if (addComma)
+ buffer.append(',');
+ switch (delta.getKind()) {
+ case IResourceDelta.ADDED :
+ buffer.append('+');
+ break;
+ case IResourceDelta.REMOVED :
+ buffer.append('-');
+ break;
+ case IResourceDelta.CHANGED :
+ buffer.append('*');
+ break;
+ }
+ buffer.append(delta.getId());
+ addComma = true;
+ }
+ }
+ }
+ buffer.append(']');
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceDeltaFactory.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceDeltaFactory.java
new file mode 100644
index 0000000000..21978e288b
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceDeltaFactory.java
@@ -0,0 +1,184 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * James Blackburn (Broadcom Corp.) - ongoing development
+ *******************************************************************************/
+package org.eclipse.core.internal.events;
+
+import java.util.Map;
+import org.eclipse.core.internal.dtree.DeltaDataTree;
+import org.eclipse.core.internal.dtree.NodeComparison;
+import org.eclipse.core.internal.resources.*;
+import org.eclipse.core.internal.watson.ElementTree;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResourceDelta;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Path;
+
+/**
+ * This class is used for calculating and building resource delta trees for notification
+ * and build purposes.
+ */
+public class ResourceDeltaFactory {
+ /**
+ * Singleton indicating no delta children
+ */
+ protected static final ResourceDelta[] NO_CHILDREN = new ResourceDelta[0];
+
+ /**
+ * Returns the resource delta representing the changes made between the given old and new trees,
+ * starting from the given root element.
+ * @param markerGeneration the start generation for which deltas should be computed, or -1
+ * if marker deltas should not be provided.
+ */
+ public static ResourceDelta computeDelta(Workspace workspace, ElementTree oldTree, ElementTree newTree, IPath root, long markerGeneration) {
+ //compute the underlying delta tree.
+ ResourceComparator comparator = markerGeneration >= 0 ? ResourceComparator.getNotificationComparator() : ResourceComparator.getBuildComparator();
+ newTree.immutable();
+ DeltaDataTree delta = null;
+ if (Path.ROOT.equals(root))
+ delta = newTree.getDataTree().compareWith(oldTree.getDataTree(), comparator);
+ else
+ delta = newTree.getDataTree().compareWith(oldTree.getDataTree(), comparator, root);
+
+ delta = delta.asReverseComparisonTree(comparator);
+ IPath pathInTree = root.isRoot() ? Path.ROOT : root;
+ IPath pathInDelta = Path.ROOT;
+
+ // get the marker deltas for the delta info object....if needed
+ Map allMarkerDeltas = null;
+ if (markerGeneration >= 0)
+ allMarkerDeltas = workspace.getMarkerManager().getMarkerDeltas(markerGeneration);
+
+ //recursively walk the delta and create a tree of ResourceDelta objects.
+ ResourceDeltaInfo deltaInfo = new ResourceDeltaInfo(workspace, allMarkerDeltas, comparator);
+ ResourceDelta result = createDelta(workspace, delta, deltaInfo, pathInTree, pathInDelta);
+
+ //compute node ID map and fix up moves
+ deltaInfo.setNodeIDMap(computeNodeIDMap(result, new NodeIDMap()));
+ result.fixMovesAndMarkers(oldTree);
+
+ // check all the projects and if they were added and opened then tweek the flags
+ // so the delta reports both.
+ int segmentCount = result.getFullPath().segmentCount();
+ if (segmentCount <= 1)
+ checkForOpen(result, segmentCount);
+ return result;
+ }
+
+ /**
+ * Checks to see if added projects were also opens and tweaks the flags
+ * accordingly. Should only be called for root and projects. Pass the segment count
+ * in since we've already calculated it before.
+ */
+ protected static void checkForOpen(ResourceDelta delta, int segmentCount) {
+ if (delta.getKind() == IResourceDelta.ADDED)
+ if (delta.newInfo.isSet(ICoreConstants.M_OPEN))
+ delta.status |= IResourceDelta.OPEN;
+ // return for PROJECT
+ if (segmentCount == 1)
+ return;
+ // recurse for ROOT
+ IResourceDelta[] children = delta.children;
+ for (int i = 0; i < children.length; i++)
+ checkForOpen((ResourceDelta) children[i], 1);
+ }
+
+ /**
+ * Creates the map from node id to element id for the old and new states.
+ * Used for recognizing moves. Returns the map.
+ */
+ protected static NodeIDMap computeNodeIDMap(ResourceDelta delta, NodeIDMap nodeIDMap) {
+ IResourceDelta[] children = delta.children;
+ for (int i = 0; i < children.length; i++) {
+ ResourceDelta child = (ResourceDelta) children[i];
+ IPath path = child.getFullPath();
+ switch (child.getKind()) {
+ case IResourceDelta.ADDED :
+ nodeIDMap.putNewPath(child.newInfo.getNodeId(), path);
+ break;
+ case IResourceDelta.REMOVED :
+ nodeIDMap.putOldPath(child.oldInfo.getNodeId(), path);
+ break;
+ case IResourceDelta.CHANGED :
+ long oldID = child.oldInfo.getNodeId();
+ long newID = child.newInfo.getNodeId();
+ //don't add entries to the map if nothing has changed.
+ if (oldID != newID) {
+ nodeIDMap.putOldPath(oldID, path);
+ nodeIDMap.putNewPath(newID, path);
+ }
+ break;
+ }
+ //recurse
+ computeNodeIDMap(child, nodeIDMap);
+ }
+ return nodeIDMap;
+ }
+
+ /**
+ * Recursively creates the tree of ResourceDelta objects rooted at
+ * the given path.
+ */
+ protected static ResourceDelta createDelta(Workspace workspace, DeltaDataTree delta, ResourceDeltaInfo deltaInfo, IPath pathInTree, IPath pathInDelta) {
+ // create the delta and fill it with information
+ ResourceDelta result = new ResourceDelta(pathInTree, deltaInfo);
+
+ // fill the result with information
+ NodeComparison compare = (NodeComparison) delta.getData(pathInDelta);
+ int comparison = compare.getUserComparison();
+ result.setStatus(comparison);
+ if (comparison == IResourceDelta.NO_CHANGE || Path.ROOT.equals(pathInTree)) {
+ ResourceInfo info = workspace.getResourceInfo(pathInTree, true, false);
+ result.setOldInfo(info);
+ result.setNewInfo(info);
+ } else {
+ result.setOldInfo((ResourceInfo) compare.getOldData());
+ result.setNewInfo((ResourceInfo) compare.getNewData());
+ }
+ // recurse over the children
+ IPath[] childKeys = delta.getChildren(pathInDelta);
+ int numChildren = childKeys.length;
+ if (numChildren == 0) {
+ result.setChildren(NO_CHILDREN);
+ } else {
+ ResourceDelta[] children = new ResourceDelta[numChildren];
+ for (int i = 0; i < numChildren; i++) {
+ //reuse the delta path if tree-relative and delta-relative are the same
+ IPath newTreePath = pathInTree == pathInDelta ? childKeys[i] : pathInTree.append(childKeys[i].lastSegment());
+ children[i] = createDelta(workspace, delta, deltaInfo, newTreePath, childKeys[i]);
+ }
+ result.setChildren(children);
+ }
+
+ // if this delta has children but no other changes, mark it as changed
+ int status = result.status;
+ if ((status & IResourceDelta.ALL_WITH_PHANTOMS) == 0 && numChildren != 0)
+ result.setStatus(status |= IResourceDelta.CHANGED);
+
+ // return the delta
+ return result;
+ }
+
+ /**
+ * Returns an empty build delta describing the fact that no
+ * changes occurred in the given project. The returned delta
+ * is not appropriate for use as a notification delta because
+ * it is rooted at a project, and does not contain marker deltas.
+ */
+ public static IResourceDelta newEmptyDelta(IProject project) {
+ ResourceDelta result = new ResourceDelta(project.getFullPath(), new ResourceDeltaInfo(((Workspace) project.getWorkspace()), null, ResourceComparator.getBuildComparator()));
+ result.setStatus(0);
+ result.setChildren(NO_CHILDREN);
+ ResourceInfo info = ((Project) project).getResourceInfo(true, false);
+ result.setOldInfo(info);
+ result.setNewInfo(info);
+ return result;
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceDeltaInfo.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceDeltaInfo.java
new file mode 100644
index 0000000000..89daee620b
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceDeltaInfo.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * James Blackburn (Broadcom Corp.) - ongoing development
+ *******************************************************************************/
+package org.eclipse.core.internal.events;
+
+import java.util.Map;
+import org.eclipse.core.internal.resources.MarkerSet;
+import org.eclipse.core.internal.resources.Workspace;
+import org.eclipse.core.runtime.IPath;
+
+public class ResourceDeltaInfo {
+ protected Workspace workspace;
+ protected Map allMarkerDeltas;
+ protected NodeIDMap nodeIDMap;
+ protected ResourceComparator comparator;
+
+ public ResourceDeltaInfo(Workspace workspace, Map markerDeltas, ResourceComparator comparator) {
+ super();
+ this.workspace = workspace;
+ this.allMarkerDeltas = markerDeltas;
+ this.comparator = comparator;
+ }
+
+ public ResourceComparator getComparator() {
+ return comparator;
+ }
+
+ /**
+ * Table of all marker deltas, IPath -> MarkerSet
+ */
+ public Map getMarkerDeltas() {
+ return allMarkerDeltas;
+ }
+
+ public NodeIDMap getNodeIDMap() {
+ return nodeIDMap;
+ }
+
+ public Workspace getWorkspace() {
+ return workspace;
+ }
+
+ public void setMarkerDeltas(Map value) {
+ allMarkerDeltas = value;
+ }
+
+ public void setNodeIDMap(NodeIDMap map) {
+ nodeIDMap = map;
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceStats.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceStats.java
new file mode 100644
index 0000000000..54e80d3baf
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceStats.java
@@ -0,0 +1,96 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2005 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.events;
+
+import org.eclipse.core.resources.*;
+import org.eclipse.core.runtime.PerformanceStats;
+
+/**
+ * An ResourceStats collects and aggregates timing data about an event such as
+ * a builder running, an editor opening, etc.
+ */
+public class ResourceStats {
+ /**
+ * The event that is currently occurring, maybe null
+ */
+ private static PerformanceStats currentStats;
+ //performance event names
+ public static final String EVENT_BUILDERS = ResourcesPlugin.PI_RESOURCES + "/perf/builders"; //$NON-NLS-1$
+ public static final String EVENT_LISTENERS = ResourcesPlugin.PI_RESOURCES + "/perf/listeners"; //$NON-NLS-1$
+ public static final String EVENT_SAVE_PARTICIPANTS = ResourcesPlugin.PI_RESOURCES + "/perf/save.participants"; //$NON-NLS-1$
+ public static final String EVENT_SNAPSHOT = ResourcesPlugin.PI_RESOURCES + "/perf/snapshot"; //$NON-NLS-1$
+
+ //performance event enablement
+ public static boolean TRACE_BUILDERS = PerformanceStats.isEnabled(ResourceStats.EVENT_BUILDERS);
+ public static boolean TRACE_LISTENERS = PerformanceStats.isEnabled(ResourceStats.EVENT_LISTENERS);
+ public static boolean TRACE_SAVE_PARTICIPANTS = PerformanceStats.isEnabled(ResourceStats.EVENT_SAVE_PARTICIPANTS);
+ public static boolean TRACE_SNAPSHOT = PerformanceStats.isEnabled(ResourceStats.EVENT_SNAPSHOT);
+
+ public static void endBuild() {
+ if (currentStats != null)
+ currentStats.endRun();
+ currentStats = null;
+ }
+
+ public static void endNotify() {
+ if (currentStats != null)
+ currentStats.endRun();
+ currentStats = null;
+ }
+
+ public static void endSave() {
+ if (currentStats != null)
+ currentStats.endRun();
+ currentStats = null;
+ }
+
+ public static void endSnapshot() {
+ if (currentStats != null)
+ currentStats.endRun();
+ currentStats = null;
+ }
+
+ /**
+ * Notifies the stats tool that a resource change listener has been added.
+ */
+ public static void listenerAdded(IResourceChangeListener listener) {
+ if (listener != null)
+ PerformanceStats.getStats(EVENT_LISTENERS, listener.getClass().getName());
+ }
+
+ /**
+ * Notifies the stats tool that a resource change listener has been removed.
+ */
+ public static void listenerRemoved(IResourceChangeListener listener) {
+ if (listener != null)
+ PerformanceStats.removeStats(EVENT_LISTENERS, listener.getClass().getName());
+ }
+
+ public static void startBuild(IncrementalProjectBuilder builder) {
+ currentStats = PerformanceStats.getStats(EVENT_BUILDERS, builder);
+ currentStats.startRun(builder.getProject().getName());
+ }
+
+ public static void startNotify(IResourceChangeListener listener) {
+ currentStats = PerformanceStats.getStats(EVENT_LISTENERS, listener);
+ currentStats.startRun();
+ }
+
+ public static void startSnapshot() {
+ currentStats = PerformanceStats.getStats(EVENT_SNAPSHOT, ResourcesPlugin.getWorkspace());
+ currentStats.startRun();
+ }
+
+ public static void startSave(ISaveParticipant participant) {
+ currentStats = PerformanceStats.getStats(EVENT_SAVE_PARTICIPANTS, participant);
+ currentStats.startRun();
+ }
+}
\ No newline at end of file
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/BlobStore.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/BlobStore.java
new file mode 100644
index 0000000000..2c9d6fcd64
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/BlobStore.java
@@ -0,0 +1,137 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.localstore;
+
+import java.io.InputStream;
+import java.util.Set;
+import org.eclipse.core.filesystem.EFS;
+import org.eclipse.core.filesystem.IFileStore;
+import org.eclipse.core.internal.utils.UniversalUniqueIdentifier;
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.core.runtime.CoreException;
+
+/**
+ * Blob store which maps UUIDs to blobs on disk. The UUID is mapped
+ * to a file in the file-system and the blob is the file contents. For scalability,
+ * the blobs are split among 255 directories with the names 00 to FF.
+ */
+public class BlobStore {
+ protected IFileStore localStore;
+
+ /** Limits the range of directories' names. */
+ protected byte mask;
+
+ //private static short[] randomArray = {213, 231, 37, 85, 211, 29, 161, 175, 187, 3, 147, 246, 170, 30, 202, 183, 242, 47, 254, 189, 25, 248, 193, 2, 119, 133, 125, 12, 76, 213, 219, 79, 69, 133, 202, 80, 150, 190, 157, 190, 80, 190, 219, 150, 169, 117, 95, 10, 77, 214, 233, 70, 5, 188, 44, 91, 165, 149, 177, 93, 17, 112, 4, 41, 230, 148, 188, 107, 213, 31, 52, 60, 111, 246, 226, 121, 129, 197, 144, 248, 92, 133, 96, 116, 104, 67, 74, 144, 185, 141, 96, 34, 182, 90, 36, 217, 28, 205, 107, 52, 201, 14, 8, 1, 27, 216, 60, 35, 251, 194, 7, 156, 32, 5, 145, 29, 96, 61, 110, 145, 50, 56, 235, 239, 170, 138, 17, 211, 56, 98, 101, 126, 27, 57, 211, 144, 206, 207, 179, 111, 160, 50, 243, 69, 106, 118, 155, 159, 28, 57, 11, 175, 43, 173, 96, 181, 99, 169, 171, 156, 246, 243, 30, 198, 251, 81, 77, 92, 160, 235, 215, 187, 23, 71, 58, 247, 127, 56, 118, 132, 79, 188, 42, 188, 158, 121, 255, 65, 154, 118, 172, 217, 4, 47, 105, 204, 135, 27, 43, 90, 9, 31, 59, 115, 193, 28, 55, 101, 9, 117, 211, 112, 61, 55, 23, 235, 51, 104, 123, 138, 76, 148, 115, 119, 81, 54, 39, 46, 149, 191, 79, 16, 222, 69, 219, 136, 148, 181, 77, 250, 101, 223, 140, 194, 141, 44, 195, 217, 31, 223, 207, 149, 245, 115, 243, 183};
+ private static byte[] randomArray = {-43, -25, 37, 85, -45, 29, -95, -81, -69, 3, -109, -10, -86, 30, -54, -73, -14, 47, -2, -67, 25, -8, -63, 2, 119, -123, 125, 12, 76, -43, -37, 79, 69, -123, -54, 80, -106, -66, -99, -66, 80, -66, -37, -106, -87, 117, 95, 10, 77, -42, -23, 70, 5, -68, 44, 91, -91, -107, -79, 93, 17, 112, 4, 41, -26, -108, -68, 107, -43, 31, 52, 60, 111, -10, -30, 121, -127, -59, -112, -8, 92, -123, 96, 116, 104, 67, 74, -112, -71, -115, 96, 34, -74, 90, 36, -39, 28, -51, 107, 52, -55, 14, 8, 1, 27, -40, 60, 35, -5, -62, 7, -100, 32, 5, -111, 29, 96, 61, 110, -111, 50, 56, -21, -17, -86, -118, 17, -45, 56, 98, 101, 126, 27, 57, -45, -112, -50, -49, -77, 111, -96, 50, -13, 69, 106, 118, -101, -97, 28, 57, 11, -81, 43, -83, 96, -75, 99, -87, -85, -100, -10, -13, 30,
+ -58, -5, 81, 77, 92, -96, -21, -41, -69, 23, 71, 58, -9, 127, 56, 118, -124, 79, -68, 42, -68, -98, 121, -1, 65, -102, 118, -84, -39, 4, 47, 105, -52, -121, 27, 43, 90, 9, 31, 59, 115, -63, 28, 55, 101, 9, 117, -45, 112, 61, 55, 23, -21, 51, 104, 123, -118, 76, -108, 115, 119, 81, 54, 39, 46, -107, -65, 79, 16, -34, 69, -37, -120, -108, -75, 77, -6, 101, -33, -116, -62, -115, 44, -61, -39, 31, -33, -49, -107, -11, 115, -13, -73,};
+
+ /**
+ * The limit is the maximum number of directories managed by this store.
+ * This number must be power of 2 and do not exceed 256. The location
+ * should be an existing valid directory.
+ */
+ public BlobStore(IFileStore store, int limit) {
+ Assert.isNotNull(store);
+ localStore = store;
+ Assert.isTrue(localStore.fetchInfo().isDirectory());
+ Assert.isTrue(limit == 256 || limit == 128 || limit == 64 || limit == 32 || limit == 16 || limit == 8 || limit == 4 || limit == 2 || limit == 1);
+ mask = (byte) (limit - 1);
+ }
+
+ public UniversalUniqueIdentifier addBlob(IFileStore target, boolean moveContents) throws CoreException {
+ UniversalUniqueIdentifier uuid = new UniversalUniqueIdentifier();
+ folderFor(uuid).mkdir(EFS.NONE, null);
+ IFileStore destination = fileFor(uuid);
+ if (moveContents)
+ target.move(destination, EFS.NONE, null);
+ else
+ target.copy(destination, EFS.NONE, null);
+ return uuid;
+ }
+
+ /* (non-Javadoc)
+ * @see UniversalUniqueIdentifier#appendByteString(StringBuffer, byte)
+ */
+ private void appendByteString(StringBuffer buffer, byte value) {
+ String hexString;
+ if (value < 0)
+ hexString = Integer.toHexString(256 + value);
+ else
+ hexString = Integer.toHexString(value);
+ if (hexString.length() == 1)
+ buffer.append("0"); //$NON-NLS-1$
+ buffer.append(hexString);
+ }
+
+ /* (non-Javadoc)
+ * Converts an array of bytes into a String.
+ *
+ * @see UniversalUniqueIdentifier#toString()
+ */
+ private String bytesToHexString(byte[] b) {
+ StringBuffer buffer = new StringBuffer();
+ for (int i = 0; i < b.length; i++)
+ appendByteString(buffer, b[i]);
+ return buffer.toString();
+ }
+
+ /**
+ * Deletes a blobFile.
+ */
+ public void deleteBlob(UniversalUniqueIdentifier uuid) {
+ Assert.isNotNull(uuid);
+ try {
+ fileFor(uuid).delete(EFS.NONE, null);
+ } catch (CoreException e) {
+ //ignore
+ }
+ }
+
+ /**
+ * Delete all of the blobs in the given set.
+ */
+ public void deleteBlobs(Set set) {
+ for (UniversalUniqueIdentifier id : set)
+ deleteBlob(id);
+ }
+
+ public IFileStore fileFor(UniversalUniqueIdentifier uuid) {
+ IFileStore root = folderFor(uuid);
+ return root.getChild(bytesToHexString(uuid.toBytes()));
+ }
+
+ /**
+ * Find out the name of the directory that fits better to this UUID.
+ */
+ public IFileStore folderFor(UniversalUniqueIdentifier uuid) {
+ byte hash = hashUUIDbytes(uuid);
+ hash &= mask; // limit the range of the directory
+ String dirName = Integer.toHexString(hash + (128 & mask)); // +(128 & mask) makes sure 00h is the lower value
+ return localStore.getChild(dirName);
+ }
+
+ public InputStream getBlob(UniversalUniqueIdentifier uuid) throws CoreException {
+ IFileStore blobFile = fileFor(uuid);
+ return blobFile.openInputStream(EFS.NONE, null);
+ }
+
+ /**
+ * Converts a byte array into a byte hash representation. It is used to
+ * get a directory name.
+ */
+ protected byte hashUUIDbytes(UniversalUniqueIdentifier uuid) {
+ byte[] bytes = uuid.toBytes();
+ byte hash = 0;
+ for (int i = 0; i < bytes.length; i++)
+ hash ^= randomArray[bytes[i] + 128]; // +128 makes sure the index is >0
+ return hash;
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/Bucket.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/Bucket.java
new file mode 100644
index 0000000000..c51fb4d752
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/Bucket.java
@@ -0,0 +1,395 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * James Blackburn (Broadcom Corp.) - ongoing development
+ *******************************************************************************/
+package org.eclipse.core.internal.localstore;
+
+import java.io.*;
+import java.util.*;
+import org.eclipse.core.internal.resources.ResourceException;
+import org.eclipse.core.internal.resources.ResourceStatus;
+import org.eclipse.core.internal.utils.FileUtil;
+import org.eclipse.core.internal.utils.Messages;
+import org.eclipse.core.resources.IResourceStatus;
+import org.eclipse.core.runtime.*;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * A bucket is a persistent dictionary having paths as keys. Values are determined
+ * by subclasses.
+ *
+ * @since 3.1
+ */
+public abstract class Bucket {
+
+ public static abstract class Entry {
+ /**
+ * This entry has not been modified in any way so far.
+ *
+ * @see #state
+ */
+ private final static int STATE_CLEAR = 0;
+ /**
+ * This entry has been requested for deletion.
+ *
+ * @see #state
+ */
+ private final static int STATE_DELETED = 0x02;
+ /**
+ * This entry has been modified.
+ *
+ * @see #state
+ */
+ private final static int STATE_DIRTY = 0x01;
+
+ /**
+ * Logical path of the object we are storing history for. This does not
+ * correspond to a file system path.
+ */
+ private IPath path;
+
+ /**
+ * State for this entry. Possible values are STATE_CLEAR, STATE_DIRTY and STATE_DELETED.
+ *
+ * @see #STATE_CLEAR
+ * @see #STATE_DELETED
+ * @see #STATE_DIRTY
+ */
+ private byte state = STATE_CLEAR;
+
+ protected Entry(IPath path) {
+ this.path = path;
+ }
+
+ public void delete() {
+ state = STATE_DELETED;
+ }
+
+ public abstract int getOccurrences();
+
+ public IPath getPath() {
+ return path;
+ }
+
+ public abstract Object getValue();
+
+ public boolean isDeleted() {
+ return state == STATE_DELETED;
+ }
+
+ public boolean isDirty() {
+ return state == STATE_DIRTY;
+ }
+
+ public boolean isEmpty() {
+ return getOccurrences() == 0;
+ }
+
+ public void markDirty() {
+ Assert.isTrue(state != STATE_DELETED);
+ state = STATE_DIRTY;
+ }
+
+ /**
+ * Called on the entry right after the visitor has visited it.
+ */
+ public void visited() {
+ // does not do anything by default
+ }
+ }
+
+ /**
+ * A visitor for bucket entries.
+ */
+ public static abstract class Visitor {
+ // should continue the traversal
+ public final static int CONTINUE = 0;
+ // should stop looking at any states immediately
+ public final static int STOP = 1;
+ // should stop looking at states for files in this container (or any of its children)
+ public final static int RETURN = 2;
+
+ /**
+ * Called after the bucket has been visited (and saved).
+ * @throws CoreException
+ */
+ public void afterSaving(Bucket bucket) throws CoreException {
+ // empty implementation, subclasses to override
+ }
+
+ /**
+ * @throws CoreException
+ */
+ public void beforeSaving(Bucket bucket) throws CoreException {
+ // empty implementation, subclasses to override
+ }
+
+ /**
+ * @return either STOP, CONTINUE or RETURN
+ */
+ public abstract int visit(Entry entry);
+ }
+
+ /**
+ * The segment name for the root directory for index files.
+ */
+ static final String INDEXES_DIR_NAME = ".indexes"; //$NON-NLS-1$
+
+ /**
+ * Map of the history entries in this bucket. Maps (String -> byte[][] or String[][]),
+ * where the key is the path of the object we are storing history for, and
+ * the value is the history entry data (UUID,timestamp) pairs.
+ */
+ private final Map entries;
+ /**
+ * The file system location of this bucket index file.
+ */
+ private File location;
+ /**
+ * Whether the in-memory bucket is dirty and needs saving
+ */
+ private boolean needSaving = false;
+ /**
+ * The project name for the bucket currently loaded. null if this is the root bucket.
+ */
+ protected String projectName;
+
+ public Bucket() {
+ this.entries = new HashMap();
+ }
+
+ /**
+ * Applies the given visitor to this bucket index.
+ * @param visitor
+ * @param filter
+ * @param depth the number of trailing segments that can differ from the filter
+ * @return one of STOP, RETURN or CONTINUE constants
+ * @exception CoreException
+ */
+ public final int accept(Visitor visitor, IPath filter, int depth) throws CoreException {
+ if (entries.isEmpty())
+ return Visitor.CONTINUE;
+ try {
+ for (Iterator> i = entries.entrySet().iterator(); i.hasNext();) {
+ Map.Entry mapEntry = i.next();
+ IPath path = new Path(mapEntry.getKey());
+ // check whether the filter applies
+ int matchingSegments = filter.matchingFirstSegments(path);
+ if (!filter.isPrefixOf(path) || path.segmentCount() - matchingSegments > depth)
+ continue;
+ // apply visitor
+ Entry bucketEntry = createEntry(path, mapEntry.getValue());
+ // calls the visitor passing all uuids for the entry
+ int outcome = visitor.visit(bucketEntry);
+ // notify the entry it has been visited
+ bucketEntry.visited();
+ if (bucketEntry.isDeleted()) {
+ needSaving = true;
+ i.remove();
+ } else if (bucketEntry.isDirty()) {
+ needSaving = true;
+ mapEntry.setValue(bucketEntry.getValue());
+ }
+ if (outcome != Visitor.CONTINUE)
+ return outcome;
+ }
+ return Visitor.CONTINUE;
+ } finally {
+ visitor.beforeSaving(this);
+ save();
+ visitor.afterSaving(this);
+ }
+ }
+
+ /**
+ * Tries to delete as many empty levels as possible.
+ */
+ private void cleanUp(File toDelete) {
+ if (!toDelete.delete())
+ // if deletion didn't go well, don't bother trying to delete the parent dir
+ return;
+ // don't try to delete beyond the root for bucket indexes
+ if (toDelete.getName().equals(INDEXES_DIR_NAME))
+ return;
+ // recurse to parent directory
+ cleanUp(toDelete.getParentFile());
+ }
+
+ /**
+ * Factory method for creating entries. Subclasses to override.
+ */
+ protected abstract Entry createEntry(IPath path, Object value);
+
+ /**
+ * Flushes this bucket so it has no contents and is not associated to any
+ * location. Any uncommitted changes are lost.
+ */
+ public void flush() {
+ projectName = null;
+ location = null;
+ entries.clear();
+ needSaving = false;
+ }
+
+ /**
+ * Returns how many entries there are in this bucket.
+ */
+ public final int getEntryCount() {
+ return entries.size();
+ }
+
+ /**
+ * Returns the value for entry corresponding to the given path (null if none found).
+ */
+ public final Object getEntryValue(String path) {
+ return entries.get(path);
+ }
+
+ /**
+ * Returns the file name used to persist the index for this bucket.
+ */
+ protected abstract String getIndexFileName();
+
+ /**
+ * Returns the version number for the file format used to persist this bucket.
+ */
+ protected abstract byte getVersion();
+
+ /**
+ * Returns the file name to be used to store bucket version information
+ */
+ protected abstract String getVersionFileName();
+
+ /**
+ * Loads the contents from a file under the given directory.
+ */
+ public void load(String newProjectName, File baseLocation) throws CoreException {
+ load(newProjectName, baseLocation, false);
+ }
+
+ /**
+ * Loads the contents from a file under the given directory. If force is
+ * false, if this bucket already contains the contents from the current location,
+ * avoids reloading.
+ */
+ public void load(String newProjectName, File baseLocation, boolean force) throws CoreException {
+ try {
+ // avoid reloading
+ if (!force && this.location != null && baseLocation.equals(this.location.getParentFile()) && (projectName == null ? (newProjectName == null) : projectName.equals(newProjectName))) {
+ this.projectName = newProjectName;
+ return;
+ }
+ // previously loaded bucket may not have been saved... save before loading new one
+ save();
+ this.projectName = newProjectName;
+ this.location = new File(baseLocation, getIndexFileName());
+ this.entries.clear();
+ if (!this.location.isFile())
+ return;
+ DataInputStream source = new DataInputStream(new BufferedInputStream(new FileInputStream(location), 8192));
+ try {
+ int version = source.readByte();
+ if (version != getVersion()) {
+ // unknown version
+ String message = NLS.bind(Messages.resources_readMetaWrongVersion, location.getAbsolutePath(), Integer.toString(version));
+ ResourceStatus status = new ResourceStatus(IResourceStatus.FAILED_READ_METADATA, message);
+ throw new ResourceException(status);
+ }
+ int entryCount = source.readInt();
+ for (int i = 0; i < entryCount; i++)
+ this.entries.put(readEntryKey(source), readEntryValue(source));
+ } finally {
+ source.close();
+ }
+ } catch (IOException ioe) {
+ String message = NLS.bind(Messages.resources_readMeta, location.getAbsolutePath());
+ ResourceStatus status = new ResourceStatus(IResourceStatus.FAILED_READ_METADATA, null, message, ioe);
+ throw new ResourceException(status);
+ }
+ }
+
+ private String readEntryKey(DataInputStream source) throws IOException {
+ if (projectName == null)
+ return source.readUTF();
+ return IPath.SEPARATOR + projectName + source.readUTF();
+ }
+
+ /**
+ * Defines how data for a given entry is to be read from a bucket file. To be implemented by subclasses.
+ */
+ protected abstract Object readEntryValue(DataInputStream source) throws IOException, CoreException;
+
+ /**
+ * Saves this bucket's contents back to its location.
+ */
+ public void save() throws CoreException {
+ if (!needSaving)
+ return;
+ try {
+ if (entries.isEmpty()) {
+ needSaving = false;
+ cleanUp(location);
+ return;
+ }
+ // ensure the parent location exists
+ File parent = location.getParentFile();
+ if (parent == null)
+ throw new IOException();//caught and rethrown below
+ parent.mkdirs();
+ DataOutputStream destination = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(location), 8192));
+ try {
+ destination.write(getVersion());
+ destination.writeInt(entries.size());
+ for (Iterator> i = entries.entrySet().iterator(); i.hasNext();) {
+ Map.Entry entry = i.next();
+ writeEntryKey(destination, entry.getKey());
+ writeEntryValue(destination, entry.getValue());
+ }
+ destination.close();
+ } finally {
+ FileUtil.safeClose(destination);
+ }
+ needSaving = false;
+ } catch (IOException ioe) {
+ String message = NLS.bind(Messages.resources_writeMeta, location.getAbsolutePath());
+ ResourceStatus status = new ResourceStatus(IResourceStatus.FAILED_WRITE_METADATA, null, message, ioe);
+ throw new ResourceException(status);
+ }
+ }
+
+ /**
+ * Sets the value for the entry with the given path. If value is null,
+ * removes the entry.
+ */
+ public final void setEntryValue(String path, Object value) {
+ if (value == null)
+ entries.remove(path);
+ else
+ entries.put(path, value);
+ needSaving = true;
+ }
+
+ private void writeEntryKey(DataOutputStream destination, String path) throws IOException {
+ if (projectName == null) {
+ destination.writeUTF(path);
+ return;
+ }
+ // omit the project name
+ int pathLength = path.length();
+ int projectLength = projectName.length();
+ String key = (pathLength == projectLength + 1) ? "" : path.substring(projectLength + 1); //$NON-NLS-1$
+ destination.writeUTF(key);
+ }
+
+ /**
+ * Defines how an entry is to be persisted to the bucket file.
+ */
+ protected abstract void writeEntryValue(DataOutputStream destination, Object entryValue) throws IOException, CoreException;
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/BucketTree.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/BucketTree.java
new file mode 100644
index 0000000000..18b54a42ee
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/BucketTree.java
@@ -0,0 +1,160 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * James Blackburn (Broadcom Corp.) - ongoing development
+ *******************************************************************************/
+package org.eclipse.core.internal.localstore;
+
+import java.io.*;
+import org.eclipse.core.internal.localstore.Bucket.Visitor;
+import org.eclipse.core.internal.resources.ResourceException;
+import org.eclipse.core.internal.resources.Workspace;
+import org.eclipse.core.internal.utils.FileUtil;
+import org.eclipse.core.internal.utils.Messages;
+import org.eclipse.core.resources.*;
+import org.eclipse.core.runtime.*;
+import org.eclipse.osgi.util.NLS;
+
+
+/**
+ * @since 3,1
+ */
+public class BucketTree {
+ public static final int DEPTH_INFINITE = Integer.MAX_VALUE;
+ public static final int DEPTH_ONE = 1;
+ public static final int DEPTH_ZERO = 0;
+
+ private final static int SEGMENT_QUOTA = 256; //two hex characters
+
+ /**
+ * Store all bucket names to avoid creating garbage when traversing the tree
+ */
+ private static final char[][] HEX_STRINGS;
+
+ static {
+ HEX_STRINGS = new char[SEGMENT_QUOTA][];
+ for (int i = 0; i < HEX_STRINGS.length; i++)
+ HEX_STRINGS[i] = Integer.toHexString(i).toCharArray();
+ }
+
+ protected Bucket current;
+
+ private Workspace workspace;
+
+ public BucketTree(Workspace workspace, Bucket bucket) {
+ this.current = bucket;
+ this.workspace = workspace;
+ }
+
+ /**
+ * From a starting point in the tree, visit all nodes under it.
+ * @param visitor
+ * @param base
+ * @param depth
+ */
+ public void accept(Bucket.Visitor visitor, IPath base, int depth) throws CoreException {
+ if (Path.ROOT.equals(base)) {
+ current.load(null, locationFor(Path.ROOT));
+ if (current.accept(visitor, base, DEPTH_ZERO) != Visitor.CONTINUE)
+ return;
+ if (depth == DEPTH_ZERO)
+ return;
+ boolean keepVisiting = true;
+ depth--;
+ IProject[] projects = workspace.getRoot().getProjects(IContainer.INCLUDE_HIDDEN);
+ for (int i = 0; keepVisiting && i < projects.length; i++) {
+ IPath projectPath = projects[i].getFullPath();
+ keepVisiting = internalAccept(visitor, projectPath, locationFor(projectPath), depth, 1);
+ }
+ } else
+ internalAccept(visitor, base, locationFor(base), depth, 0);
+ }
+
+ public void close() throws CoreException {
+ current.save();
+ saveVersion();
+ }
+
+ public Bucket getCurrent() {
+ return current;
+ }
+
+ public File getVersionFile() {
+ return new File(locationFor(Path.ROOT), current.getVersionFileName());
+ }
+
+ /**
+ * This will never be called for a bucket for the workspace root.
+ *
+ * @return whether to continue visiting other branches
+ */
+ private boolean internalAccept(Bucket.Visitor visitor, IPath base, File bucketDir, int depthRequested, int currentDepth) throws CoreException {
+ current.load(base.segment(0), bucketDir);
+ int outcome = current.accept(visitor, base, depthRequested);
+ if (outcome != Visitor.CONTINUE)
+ return outcome == Visitor.RETURN;
+ if (depthRequested <= currentDepth)
+ return true;
+ File[] subDirs = bucketDir.listFiles();
+ if (subDirs == null)
+ return true;
+ for (int i = 0; i < subDirs.length; i++)
+ if (subDirs[i].isDirectory())
+ if (!internalAccept(visitor, base, subDirs[i], depthRequested, currentDepth + 1))
+ return false;
+ return true;
+ }
+
+ public void loadBucketFor(IPath path) throws CoreException {
+ current.load(Path.ROOT.equals(path) ? null : path.segment(0), locationFor(path));
+ }
+
+ private File locationFor(IPath resourcePath) {
+ //optimized to avoid string and path creations
+ IPath baseLocation = workspace.getMetaArea().locationFor(resourcePath).removeTrailingSeparator();
+ int segmentCount = resourcePath.segmentCount();
+ String locationString = baseLocation.toOSString();
+ StringBuffer locationBuffer = new StringBuffer(locationString.length() + Bucket.INDEXES_DIR_NAME.length() + 16);
+ locationBuffer.append(locationString);
+ locationBuffer.append(File.separatorChar);
+ locationBuffer.append(Bucket.INDEXES_DIR_NAME);
+ // the last segment is ignored
+ for (int i = 1; i < segmentCount - 1; i++) {
+ // translate all segments except the first one (project name)
+ locationBuffer.append(File.separatorChar);
+ locationBuffer.append(translateSegment(resourcePath.segment(i)));
+ }
+ return new File(locationBuffer.toString());
+ }
+
+ /**
+ * Writes the version tag to a file on disk.
+ */
+ private void saveVersion() throws CoreException {
+ File versionFile = getVersionFile();
+ if (!versionFile.getParentFile().exists())
+ versionFile.getParentFile().mkdirs();
+ FileOutputStream stream = null;
+ try {
+ stream = new FileOutputStream(versionFile);
+ stream.write(current.getVersion());
+ stream.close();
+ } catch (IOException e) {
+ String message = NLS.bind(Messages.resources_writeWorkspaceMeta, versionFile.getAbsolutePath());
+ throw new ResourceException(IResourceStatus.FAILED_WRITE_METADATA, null, message, e);
+ } finally {
+ FileUtil.safeClose(stream);
+ }
+ }
+
+ private char[] translateSegment(String segment) {
+ // String.hashCode algorithm is API
+ return HEX_STRINGS[Math.abs(segment.hashCode()) % SEGMENT_QUOTA];
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/CollectSyncStatusVisitor.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/CollectSyncStatusVisitor.java
new file mode 100644
index 0000000000..1231efb97e
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/CollectSyncStatusVisitor.java
@@ -0,0 +1,116 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2014 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.localstore;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.eclipse.core.internal.resources.*;
+import org.eclipse.core.internal.utils.Messages;
+import org.eclipse.core.resources.IResourceStatus;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.*;
+import org.eclipse.osgi.util.NLS;
+
+//
+/**
+ * Visits a unified tree, and collects local sync information in
+ * a multi-status. At the end of the visit, the resource tree will NOT
+ * be synchronized with the file system, but all discrepancies between
+ * the two will be recorded in the returned status.
+ */
+public class CollectSyncStatusVisitor extends RefreshLocalVisitor {
+ protected List affectedResources;
+ /**
+ * Determines how to treat cases where the resource is missing from
+ * the local file system. When performing a deletion with force=false,
+ * we don't care about files that are out of sync because they do not
+ * exist in the file system.
+ */
+ private boolean ignoreLocalDeletions = false;
+ protected MultiStatus status;
+
+ /**
+ * Creates a new visitor, whose sync status will have the given title.
+ */
+ public CollectSyncStatusVisitor(String multiStatusTitle, IProgressMonitor monitor) {
+ super(monitor);
+ status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IStatus.INFO, multiStatusTitle, null);
+ }
+
+ protected void changed(Resource target) {
+ String message = NLS.bind(Messages.localstore_resourceIsOutOfSync, target.getFullPath());
+ status.add(new ResourceStatus(IResourceStatus.OUT_OF_SYNC_LOCAL, target.getFullPath(), message));
+ if (affectedResources == null)
+ affectedResources = new ArrayList(20);
+ affectedResources.add(target);
+ resourceChanged = true;
+ }
+
+ @Override
+ protected void createResource(UnifiedTreeNode node, Resource target) {
+ changed(target);
+ }
+
+ @Override
+ protected void deleteResource(UnifiedTreeNode node, Resource target) {
+ if (!ignoreLocalDeletions)
+ changed(target);
+ }
+
+ @Override
+ protected void fileToFolder(UnifiedTreeNode node, Resource target) {
+ changed(target);
+ }
+
+ @Override
+ protected void folderToFile(UnifiedTreeNode node, Resource target) {
+ changed(target);
+ }
+
+ /**
+ * Returns the list of resources that were not synchronized with
+ * the local file system, or null if all resources
+ * are synchronized.
+ */
+ public List getAffectedResources() {
+ return affectedResources;
+ }
+
+ /**
+ * Returns the sync status that has been collected as a result of this visit.
+ */
+ public MultiStatus getSyncStatus() {
+ return status;
+ }
+
+ @Override
+ protected void makeLocal(UnifiedTreeNode node, Resource target) {
+ changed(target);
+ }
+
+ @Override
+ protected void refresh(Container parent) {
+ changed(parent);
+ }
+
+ @Override
+ protected void resourceChanged(UnifiedTreeNode node, Resource target) {
+ changed(target);
+ }
+
+ /**
+ * Instructs this visitor to ignore changes due to local deletions
+ * in the file system.
+ */
+ public void setIgnoreLocalDeletions(boolean value) {
+ this.ignoreLocalDeletions = value;
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/CopyVisitor.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/CopyVisitor.java
new file mode 100644
index 0000000000..995b64833e
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/CopyVisitor.java
@@ -0,0 +1,211 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2015 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * Serge Beauchamp (Freescale Semiconductor) - [252996] add resource filtering
+*******************************************************************************/
+package org.eclipse.core.internal.localstore;
+
+import java.net.URI;
+import java.util.LinkedList;
+import org.eclipse.core.filesystem.EFS;
+import org.eclipse.core.filesystem.IFileStore;
+import org.eclipse.core.internal.resources.*;
+import org.eclipse.core.internal.utils.Messages;
+import org.eclipse.core.internal.utils.Policy;
+import org.eclipse.core.resources.*;
+import org.eclipse.core.runtime.*;
+import org.eclipse.osgi.util.NLS;
+
+//
+public class CopyVisitor implements IUnifiedTreeVisitor {
+
+ /** root destination */
+ protected IResource rootDestination;
+
+ /** reports progress */
+ protected IProgressMonitor monitor;
+
+ /** update flags */
+ protected int updateFlags;
+
+ /** force flag */
+ protected boolean force;
+
+ /** deep copy flag */
+ protected boolean isDeep;
+
+ /** segments to drop from the source name */
+ protected int segmentsToDrop;
+
+ /** stores problems encountered while copying */
+ protected MultiStatus status;
+
+ /** visitor to refresh unsynchronized nodes */
+ protected RefreshLocalVisitor refreshLocalVisitor;
+
+ private FileSystemResourceManager localManager;
+
+ public CopyVisitor(IResource rootSource, IResource destination, int updateFlags, IProgressMonitor monitor) {
+ this.localManager = ((Resource) rootSource).getLocalManager();
+ this.rootDestination = destination;
+ this.updateFlags = updateFlags;
+ this.isDeep = (updateFlags & IResource.SHALLOW) == 0;
+ this.force = (updateFlags & IResource.FORCE) != 0;
+ this.monitor = monitor;
+ this.segmentsToDrop = rootSource.getFullPath().segmentCount();
+ this.status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IStatus.INFO, Messages.localstore_copyProblem, null);
+ }
+
+ protected boolean copy(UnifiedTreeNode node) {
+ Resource source = (Resource) node.getResource();
+ IPath sufix = source.getFullPath().removeFirstSegments(segmentsToDrop);
+ Resource destination = getDestinationResource(source, sufix);
+ if (!copyProperties(source, destination))
+ return false;
+ return copyContents(node, source, destination);
+ }
+
+ protected boolean copyContents(UnifiedTreeNode node, Resource source, Resource destination) {
+ try {
+ if (source.isVirtual()) {
+ ((Folder) destination).create(IResource.VIRTUAL, true, null);
+ return true;
+ }
+ if ((!isDeep || source.isUnderVirtual()) && source.isLinked()) {
+ URI sourceLocationURI = getWorkspace().transferVariableDefinition(source, destination, source.getRawLocationURI());
+ destination.createLink(sourceLocationURI, updateFlags & IResource.ALLOW_MISSING_LOCAL, null);
+ return false;
+ }
+ // update filters in project descriptions
+ if (source instanceof Container && ((Container) source).hasFilters()) {
+ Project sourceProject = (Project) source.getProject();
+ LinkedList originalDescriptions = sourceProject.internalGetDescription().getFilter(source.getProjectRelativePath());
+ LinkedList filterDescriptions = FilterDescription.copy(originalDescriptions, destination);
+ Project project = (Project) destination.getProject();
+ project.internalGetDescription().setFilters(destination.getProjectRelativePath(), filterDescriptions);
+ project.writeDescription(updateFlags);
+ }
+
+ IFileStore sourceStore = node.getStore();
+ IFileStore destinationStore = destination.getStore();
+ //ensure the parent of the root destination exists (bug 126104)
+ if (destination == rootDestination)
+ destinationStore.getParent().mkdir(EFS.NONE, Policy.subMonitorFor(monitor, 0));
+ sourceStore.copy(destinationStore, EFS.SHALLOW, Policy.subMonitorFor(monitor, 0));
+ //create the destination in the workspace
+ ResourceInfo info = localManager.getWorkspace().createResource(destination, updateFlags);
+ localManager.updateLocalSync(info, destinationStore.fetchInfo().getLastModified());
+ //update timestamps on aliases
+ getWorkspace().getAliasManager().updateAliases(destination, destinationStore, IResource.DEPTH_ZERO, monitor);
+ if (destination.getType() == IResource.FILE)
+ ((File) destination).updateMetadataFiles();
+ } catch (CoreException e) {
+ status.add(e.getStatus());
+ }
+ return true;
+ }
+
+ protected boolean copyProperties(Resource target, Resource destination) {
+ try {
+ target.getPropertyManager().copy(target, destination, IResource.DEPTH_ZERO);
+ return true;
+ } catch (CoreException e) {
+ status.add(e.getStatus());
+ return false;
+ }
+ }
+
+ protected Resource getDestinationResource(Resource source, IPath suffix) {
+ if (suffix.segmentCount() == 0)
+ return (Resource) rootDestination;
+ IPath destinationPath = rootDestination.getFullPath().append(suffix);
+ return getWorkspace().newResource(destinationPath, source.getType());
+ }
+
+ /**
+ * This is done in order to generate less garbage.
+ */
+ protected RefreshLocalVisitor getRefreshLocalVisitor() {
+ if (refreshLocalVisitor == null)
+ refreshLocalVisitor = new RefreshLocalVisitor(Policy.monitorFor(null));
+ return refreshLocalVisitor;
+ }
+
+ public IStatus getStatus() {
+ return status;
+ }
+
+ protected Workspace getWorkspace() {
+ return (Workspace) rootDestination.getWorkspace();
+ }
+
+ protected boolean isSynchronized(UnifiedTreeNode node) {
+ /* virtual resources are always deemed as being synchronized */
+ if (node.getResource().isVirtual())
+ return true;
+ if (node.isErrorInFileSystem())
+ return true; // Assume synchronized unless proven otherwise
+ /* does the resource exist in workspace and file system? */
+ if (!node.existsInWorkspace() || !node.existsInFileSystem())
+ return false;
+ /* we don't care about folder last modified */
+ if (node.isFolder() && node.getResource().getType() == IResource.FOLDER)
+ return true;
+ /* is lastModified different? */
+ Resource target = (Resource) node.getResource();
+ long lastModifed = target.getResourceInfo(false, false).getLocalSyncInfo();
+ if (lastModifed != node.getLastModified())
+ return false;
+ return true;
+ }
+
+ protected void synchronize(UnifiedTreeNode node) throws CoreException {
+ getRefreshLocalVisitor().visit(node);
+ }
+
+ @Override
+ public boolean visit(UnifiedTreeNode node) throws CoreException {
+ Policy.checkCanceled(monitor);
+ int work = 1;
+ try {
+ //location can be null if based on an undefined variable
+ if (node.getStore() == null) {
+ //should still be a best effort copy
+ IPath path = node.getResource().getFullPath();
+ String message = NLS.bind(Messages.localstore_locationUndefined, path);
+ status.add(new ResourceStatus(IResourceStatus.FAILED_READ_LOCAL, path, message, null));
+ return false;
+ }
+ boolean wasSynchronized = isSynchronized(node);
+ if (force && !wasSynchronized) {
+ synchronize(node);
+ // If not synchronized, the monitor did not take this resource into account.
+ // So, do not report work on it.
+ work = 0;
+ //if source still doesn't exist, then fail because we can't copy a missing resource
+ if (!node.existsInFileSystem()) {
+ IPath path = node.getResource().getFullPath();
+ String message = NLS.bind(Messages.resources_mustExist, path);
+ status.add(new ResourceStatus(IResourceStatus.RESOURCE_NOT_FOUND, path, message, null));
+ return false;
+ }
+ }
+ if (!force && !wasSynchronized) {
+ IPath path = node.getResource().getFullPath();
+ String message = NLS.bind(Messages.localstore_resourceIsOutOfSync, path);
+ status.add(new ResourceStatus(IResourceStatus.OUT_OF_SYNC_LOCAL, path, message, null));
+ return true;
+ }
+ return copy(node);
+ } finally {
+ monitor.worked(work);
+ }
+ }
+
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/DeleteVisitor.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/DeleteVisitor.java
new file mode 100644
index 0000000000..f74bc44968
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/DeleteVisitor.java
@@ -0,0 +1,154 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2015 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * Matt McCutchen - fix for bug 174492
+ * James Blackburn (Broadcom Corp.) - ongoing development
+ *******************************************************************************/
+package org.eclipse.core.internal.localstore;
+
+import java.util.Iterator;
+import java.util.List;
+import org.eclipse.core.filesystem.*;
+import org.eclipse.core.filesystem.provider.FileInfo;
+import org.eclipse.core.internal.resources.ICoreConstants;
+import org.eclipse.core.internal.resources.Resource;
+import org.eclipse.core.internal.utils.Messages;
+import org.eclipse.core.internal.utils.Policy;
+import org.eclipse.core.resources.*;
+import org.eclipse.core.runtime.*;
+import org.eclipse.osgi.util.NLS;
+
+public class DeleteVisitor implements IUnifiedTreeVisitor, ICoreConstants {
+ protected boolean force;
+ protected boolean keepHistory;
+ protected IProgressMonitor monitor;
+ protected List skipList;
+ protected MultiStatus status;
+
+ /**
+ * The number of tickets available on the progress monitor
+ */
+ private int ticks;
+
+ public DeleteVisitor(List skipList, int flags, IProgressMonitor monitor, int ticks) {
+ this.skipList = skipList;
+ this.ticks = ticks;
+ this.force = (flags & IResource.FORCE) != 0;
+ this.keepHistory = (flags & IResource.KEEP_HISTORY) != 0;
+ this.monitor = monitor;
+ status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.FAILED_DELETE_LOCAL, Messages.localstore_deleteProblem, null);
+ }
+
+ /**
+ * Deletes a file from both the workspace resource tree and the file system.
+ */
+ protected void delete(UnifiedTreeNode node, boolean shouldKeepHistory) {
+ Resource target = (Resource) node.getResource();
+ try {
+ final boolean deleteLocalFile = !target.isLinked() && node.existsInFileSystem();
+ IFileStore localFile = deleteLocalFile ? node.getStore() : null;
+ if (deleteLocalFile && shouldKeepHistory)
+ recursiveKeepHistory(target.getLocalManager().getHistoryStore(), node);
+ node.removeChildrenFromTree();
+ //delete from disk
+ int work = ticks < 0 ? 0 : ticks;
+ ticks -= work;
+ if (deleteLocalFile)
+ localFile.delete(EFS.NONE, Policy.subMonitorFor(monitor, work));
+ else
+ monitor.worked(work);
+ //delete from tree
+ if (node.existsInWorkspace())
+ target.deleteResource(true, status);
+ } catch (CoreException e) {
+ status.add(e.getStatus());
+ // delete might have been partly successful, so refresh to ensure in sync
+ try {
+ target.refreshLocal(IResource.DEPTH_INFINITE, null);
+ } catch (CoreException e1) {
+ //ignore secondary failure - we are just trying to cleanup from first failure
+ }
+ }
+ }
+
+ /**
+ * Only consider path in equality in order to handle gender changes
+ */
+ protected boolean equals(IResource one, IResource another) {
+ return one.getFullPath().equals(another.getFullPath());
+ }
+
+ public MultiStatus getStatus() {
+ return status;
+ }
+
+ protected boolean isAncestor(IResource one, IResource another) {
+ return one.getFullPath().isPrefixOf(another.getFullPath()) && !equals(one, another);
+ }
+
+ protected boolean isAncestorOfResourceToSkip(IResource resource) {
+ if (skipList == null)
+ return false;
+ for (IResource target : skipList) {
+ if (isAncestor(resource, target))
+ return true;
+ }
+ return false;
+ }
+
+ private void recursiveKeepHistory(IHistoryStore store, UnifiedTreeNode node) {
+ final IResource target = node.getResource();
+ //we don't delete linked content, so no need to keep history
+ if (target.isLinked() || target.isVirtual() || node.isSymbolicLink())
+ return;
+ if (node.isFolder()) {
+ monitor.subTask(NLS.bind(Messages.localstore_deleting, target.getFullPath()));
+ for (Iterator children = node.getChildren(); children.hasNext();)
+ recursiveKeepHistory(store, children.next());
+ } else {
+ IFileInfo info = node.fileInfo;
+ if (info == null)
+ info = new FileInfo(node.getLocalName());
+ store.addState(target.getFullPath(), node.getStore(), info, true);
+ }
+ monitor.worked(1);
+ ticks--;
+ }
+
+ protected void removeFromSkipList(IResource resource) {
+ if (skipList != null)
+ skipList.remove(resource);
+ }
+
+ protected boolean shouldSkip(IResource resource) {
+ if (skipList == null)
+ return false;
+ for (int i = 0; i < skipList.size(); i++)
+ if (equals(resource, skipList.get(i)))
+ return true;
+ return false;
+ }
+
+ @Override
+ public boolean visit(UnifiedTreeNode node) {
+ Policy.checkCanceled(monitor);
+ Resource target = (Resource) node.getResource();
+ if (shouldSkip(target)) {
+ removeFromSkipList(target);
+ int skipTicks = target.countResources(IResource.DEPTH_INFINITE, false);
+ monitor.worked(skipTicks);
+ ticks -= skipTicks;
+ return false;
+ }
+ if (isAncestorOfResourceToSkip(target))
+ return true;
+ delete(node, keepHistory);
+ return false;
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/FileStoreRoot.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/FileStoreRoot.java
new file mode 100644
index 0000000000..88f31aa572
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/FileStoreRoot.java
@@ -0,0 +1,199 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2014 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * Serge Beauchamp (Freescale Semiconductor) - [229633] Project Path Variable Support
+ * Sergey Prigogin (Google) - [338010] Resource.createLink() does not preserve symbolic links
+ *******************************************************************************/
+package org.eclipse.core.internal.localstore;
+
+import java.io.File;
+import java.net.URI;
+import org.eclipse.core.filesystem.EFS;
+import org.eclipse.core.filesystem.IFileStore;
+import org.eclipse.core.internal.utils.FileUtil;
+import org.eclipse.core.resources.*;
+import org.eclipse.core.runtime.*;
+
+/**
+ * Represents the root of a file system that is connected to the workspace.
+ * A file system can be rooted on any resource.
+ */
+public class FileStoreRoot {
+ private int chop;
+ /**
+ * When a root is changed, the old root object is marked invalid
+ * so that other resources with a cache of the root will know they need to update.
+ */
+ private boolean isValid = true;
+ /**
+ * If this root represents a resource in the local file system, this path
+ * represents the root location. This value is null if the root represents
+ * a non-local file system
+ */
+ private IPath localRoot;
+ /**
+ * Canonicalized version of localRoot. Initialized lazily.
+ * @see FileUtil#canonicalPath(IPath)
+ */
+ private IPath canonicalLocalRoot;
+
+ private URI root;
+ /**
+ * Canonicalized version of root. Initialized lazily.
+ * @see FileUtil#canonicalURI(URI)
+ */
+ private URI canonicalRoot;
+
+ /**
+ * Defines the root of a file system within the workspace tree.
+ * @param rootURI The virtual file representing the root of the file
+ * system that has been mounted
+ * @param workspacePath The workspace path at which this file
+ * system has been mounted
+ */
+ FileStoreRoot(URI rootURI, IPath workspacePath) {
+ Assert.isNotNull(rootURI);
+ Assert.isNotNull(workspacePath);
+ this.root = rootURI;
+ this.chop = workspacePath.segmentCount();
+ this.localRoot = toLocalPath(root);
+ }
+
+ private IPathVariableManager getManager(IPath workspacePath) {
+ IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
+ IResource resource = workspaceRoot.findMember(workspacePath);
+ if (resource != null)
+ return resource.getPathVariableManager();
+ return workspaceRoot.getFile(workspacePath).getPathVariableManager();
+ }
+
+ /**
+ * Returns the resolved, absolute file system location of the resource
+ * corresponding to the given workspace path, or null if none could
+ * be computed. No canonicalization is applied to the returned URI.
+ */
+ public URI computeURI(IPath workspacePath) {
+ return computeURI(workspacePath, false);
+ }
+
+ /**
+ * Returns the resolved, absolute file system location of the resource
+ * corresponding to the given workspace path, or null if none could
+ * be computed.
+ *
+ * @param workspacePath the workspace path to compute the URL for
+ * @param canonical if {@code true}, the prefix of the path of the returned URI
+ * corresponding to this root will be canonicalized
+ */
+ public URI computeURI(IPath workspacePath, boolean canonical) {
+ IPath childPath = workspacePath.removeFirstSegments(chop);
+ URI rootURI = canonical ? getCanonicalRoot() : root;
+ rootURI = getManager(workspacePath).resolveURI(rootURI);
+ if (childPath.segmentCount() == 0)
+ return rootURI;
+ try {
+ return EFS.getStore(rootURI).getFileStore(childPath).toURI();
+ } catch (CoreException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Creates an IFileStore for a given workspace path. The prefix of the path of
+ * the returned IFileStore corresponding to this root is canonicalized.
+ * @exception CoreException If the file system for that resource is undefined
+ */
+ IFileStore createStore(IPath workspacePath, IResource resource) throws CoreException {
+ IPath childPath = workspacePath.removeFirstSegments(chop);
+ IFileStore rootStore;
+ final URI uri = resource.getPathVariableManager().resolveURI(getCanonicalRoot());
+ if (!uri.isAbsolute()) {
+ //handles case where resource location cannot be resolved
+ //such as unresolved path variable or invalid file system scheme
+ return EFS.getNullFileSystem().getStore(workspacePath);
+ }
+ rootStore = EFS.getStore(uri);
+ if (childPath.segmentCount() == 0)
+ return rootStore;
+ return rootStore.getFileStore(childPath);
+ }
+
+ boolean isValid() {
+ return isValid;
+ }
+
+ /**
+ * Returns the resolved, absolute file system location of the given resource.
+ * Returns null if the location could not be resolved. No canonicalization
+ * is applied to the returned path.
+ *
+ * @param workspacePath the workspace path of the resource
+ * @param resource the resource itself
+ */
+ IPath localLocation(IPath workspacePath, IResource resource) {
+ return localLocation(workspacePath, resource, false);
+ }
+
+ /**
+ * Returns the resolved, absolute file system location of the given resource.
+ * Returns null if the location could not be resolved.
+ *
+ * @param workspacePath the workspace path of the resource
+ * @param resource the resource itself
+ * @param canonical if {@code true}, the prefix of the returned path corresponding
+ * to this root will be canonicalized
+ */
+ IPath localLocation(IPath workspacePath, IResource resource, boolean canonical) {
+ if (localRoot == null)
+ return null;
+ IPath rootPath = canonical ? getCanonicalLocalRoot() : localRoot;
+ IPath location;
+ if (workspacePath.segmentCount() <= chop)
+ location = rootPath;
+ else
+ location = rootPath.append(workspacePath.removeFirstSegments(chop));
+ location = resource.getPathVariableManager().resolvePath(location);
+
+ // if path is still relative then path variable could not be resolved
+ // if path is null, it means path variable refers to a non-local filesystem
+ if (location == null || !location.isAbsolute())
+ return null;
+ return location;
+ }
+
+ void setValid(boolean value) {
+ this.isValid = value;
+ }
+
+ /**
+ * Returns the local path for the given URI, or null if not possible.
+ */
+ private IPath toLocalPath(URI uri) {
+ try {
+ final File localFile = EFS.getStore(uri).toLocalFile(EFS.NONE, null);
+ return localFile == null ? null : new Path(localFile.getAbsolutePath());
+ } catch (CoreException e) {
+ return FileUtil.toPath(uri);
+ }
+ }
+
+ private synchronized IPath getCanonicalLocalRoot() {
+ if (canonicalLocalRoot == null && localRoot != null) {
+ canonicalLocalRoot = FileUtil.canonicalPath(localRoot);
+ }
+ return canonicalLocalRoot;
+ }
+
+ private synchronized URI getCanonicalRoot() {
+ if (canonicalRoot == null) {
+ canonicalRoot = FileUtil.canonicalURI(root);
+ }
+ return canonicalRoot;
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/FileSystemResourceManager.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/FileSystemResourceManager.java
new file mode 100644
index 0000000000..574b452e9f
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/FileSystemResourceManager.java
@@ -0,0 +1,1213 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2015 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * Martin Oberhuber (Wind River) - [210664] descriptionChanged(): ignore LF style
+ * Martin Oberhuber (Wind River) - [233939] findFilesForLocation() with symlinks
+ * James Blackburn (Broadcom Corp.) - ongoing development
+ * Sergey Prigogin (Google) - [338010] Resource.createLink() does not preserve symbolic links
+ *******************************************************************************/
+package org.eclipse.core.internal.localstore;
+
+import java.io.*;
+import java.net.URI;
+import java.util.*;
+import org.eclipse.core.filesystem.*;
+import org.eclipse.core.filesystem.URIUtil;
+import org.eclipse.core.internal.resources.*;
+import org.eclipse.core.internal.resources.File;
+import org.eclipse.core.internal.utils.*;
+import org.eclipse.core.resources.*;
+import org.eclipse.core.runtime.*;
+import org.eclipse.core.runtime.Preferences.PropertyChangeEvent;
+import org.eclipse.osgi.util.NLS;
+import org.xml.sax.InputSource;
+
+/**
+ * Manages the synchronization between the workspace's view and the file system.
+ */
+public class FileSystemResourceManager implements ICoreConstants, IManager, Preferences.IPropertyChangeListener {
+
+ /**
+ * The history store is initialized lazily - always use the accessor method
+ */
+ protected IHistoryStore _historyStore;
+ protected Workspace workspace;
+
+ private volatile boolean lightweightAutoRefreshEnabled;
+
+ public FileSystemResourceManager(Workspace workspace) {
+ this.workspace = workspace;
+ }
+
+ /**
+ * Returns the workspace paths of all resources that may correspond to
+ * the given file system location. Returns an empty ArrayList if there are no
+ * such paths. This method does not consider whether resources actually
+ * exist at the given locations.
+ *
+ * The workspace paths of {@link IResource#HIDDEN} project and resources
+ * located in {@link IResource#HIDDEN} projects won't be added to the result.
+ *
+ *
+ */
+ protected ArrayList allPathsForLocation(URI inputLocation) {
+ URI canonicalLocation = FileUtil.canonicalURI(inputLocation);
+ // First, try the canonical version of the inputLocation.
+ // If the inputLocation is different from the canonical version, it will be tried second
+ ArrayList results = allPathsForLocationNonCanonical(canonicalLocation);
+ if (results.size() == 0 && canonicalLocation != inputLocation) {
+ results = allPathsForLocationNonCanonical(inputLocation);
+ }
+ return results;
+ }
+
+ private ArrayList allPathsForLocationNonCanonical(URI inputLocation) {
+ URI location = inputLocation;
+ final boolean isFileLocation = EFS.SCHEME_FILE.equals(inputLocation.getScheme());
+ final IWorkspaceRoot root = getWorkspace().getRoot();
+ final ArrayList results = new ArrayList();
+ if (URIUtil.equals(location, locationURIFor(root, true))) {
+ //there can only be one resource at the workspace root's location
+ results.add(Path.ROOT);
+ return results;
+ }
+ IProject[] projects = root.getProjects(IContainer.INCLUDE_HIDDEN);
+ for (int i = 0; i < projects.length; i++) {
+ IProject project = projects[i];
+ if (!project.exists())
+ continue;
+ //check the project location
+ URI testLocation = locationURIFor(project, true);
+ if (testLocation == null)
+ continue;
+ boolean usingAnotherScheme = !inputLocation.getScheme().equals(testLocation.getScheme());
+ // if we are looking for file: locations try to get a file: location for this project
+ if (isFileLocation && !EFS.SCHEME_FILE.equals(testLocation.getScheme()))
+ testLocation = getFileURI(testLocation);
+ if (testLocation == null)
+ continue;
+ URI relative = testLocation.relativize(location);
+ if (!relative.isAbsolute() && !relative.equals(testLocation)) {
+ IPath suffix = new Path(relative.getPath());
+ results.add(project.getFullPath().append(suffix));
+ }
+ if (usingAnotherScheme) {
+ // if a different scheme is used, we can't use the AliasManager, since the manager
+ // map is stored using the EFS scheme, and not necessarily the SCHEME_FILE
+ ProjectDescription description = ((Project) project).internalGetDescription();
+ if (description == null)
+ continue;
+ HashMap links = description.getLinks();
+ if (links == null)
+ continue;
+ for (LinkDescription link : links.values()) {
+ IResource resource = project.findMember(link.getProjectRelativePath());
+ IPathVariableManager pathMan = resource == null ? project.getPathVariableManager() : resource.getPathVariableManager();
+ testLocation = pathMan.resolveURI(link.getLocationURI());
+ // if we are looking for file: locations try to get a file: location for this link
+ if (isFileLocation && !EFS.SCHEME_FILE.equals(testLocation.getScheme()))
+ testLocation = getFileURI(testLocation);
+ if (testLocation == null)
+ continue;
+ relative = testLocation.relativize(location);
+ if (!relative.isAbsolute() && !relative.equals(testLocation)) {
+ IPath suffix = new Path(relative.getPath());
+ results.add(project.getFullPath().append(link.getProjectRelativePath()).append(suffix));
+ }
+ }
+ }
+ }
+ try {
+ findLinkedResourcesPaths(inputLocation, results);
+ } catch (CoreException e) {
+ Policy.log(e);
+ }
+ return results;
+ }
+
+ /**
+ * Asynchronously auto-refresh the requested resource if {@link ResourcesPlugin#PREF_LIGHTWEIGHT_AUTO_REFRESH} is enabled.
+ * @param target
+ */
+ private void asyncRefresh(IResource target) {
+ if (lightweightAutoRefreshEnabled)
+ workspace.getRefreshManager().refresh(target);
+ }
+
+ private void findLinkedResourcesPaths(URI inputLocation, final ArrayList results) throws CoreException {
+ IPath suffix = null;
+ IFileStore fileStore = EFS.getStore(inputLocation);
+ while (fileStore != null) {
+ IResource[] resources = workspace.getAliasManager().findResources(fileStore);
+ for (int i = 0; i < resources.length; i++) {
+ if (resources[i].isLinked()) {
+ IPath path = resources[i].getFullPath();
+ if (suffix != null)
+ path = path.append(suffix);
+ if (!results.contains(path))
+ results.add(path);
+ }
+ }
+ if (suffix == null)
+ suffix = Path.fromPortableString(fileStore.getName());
+ else
+ suffix = Path.fromPortableString(fileStore.getName()).append(suffix);
+ fileStore = fileStore.getParent();
+ }
+ }
+
+ /**
+ * Tries to obtain a file URI for the given URI. Returns null if the file system associated
+ * to the URI scheme does not map to the local file system.
+ * @param locationURI the URI to convert
+ * @return a file URI or null
+ */
+ private URI getFileURI(URI locationURI) {
+ try {
+ IFileStore testLocationStore = EFS.getStore(locationURI);
+ java.io.File storeAsFile = testLocationStore.toLocalFile(EFS.NONE, null);
+ if (storeAsFile != null)
+ return URIUtil.toURI(storeAsFile.getAbsolutePath());
+ } catch (CoreException e) {
+ // we don't know such file system or some other failure, just return null
+ }
+ return null;
+ }
+
+ /**
+ * Returns all resources that correspond to the given file system location,
+ * including resources under linked resources. Returns an empty array if
+ * there are no corresponding resources.
+ *
+ * If the {@link IContainer#INCLUDE_TEAM_PRIVATE_MEMBERS} flag is specified
+ * in the member flags, team private members will be included along with the
+ * others. If the {@link IContainer#INCLUDE_TEAM_PRIVATE_MEMBERS} flag is
+ * not specified (recommended), the result will omit any team private member
+ * resources.
+ *
+ *
+ * If the {@link IContainer#INCLUDE_HIDDEN} flag is specified in the member
+ * flags, hidden members will be included along with the others. If the
+ * {@link IContainer#INCLUDE_HIDDEN} flag is not specified (recommended),
+ * the result will omit any hidden member resources.
+ *
+ *
+ * The result will also omit resources that are explicitly excluded
+ * from the workspace according to existing resource filters.
+ *
+ *
+ * @param location
+ * the file system location
+ * @param files
+ * resources that may exist below the project level can be either
+ * files or folders. If this parameter is true, files will be
+ * returned, otherwise containers will be returned.
+ * @param memberFlags
+ * bit-wise or of member flag constants (
+ * {@link IContainer#INCLUDE_TEAM_PRIVATE_MEMBERS} and
+ * {@link IContainer#INCLUDE_HIDDEN}) indicating which members are of
+ * interest
+ */
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ public IResource[] allResourcesFor(URI location, boolean files, int memberFlags) {
+ ArrayList result = allPathsForLocation(location);
+ int count = 0;
+ for (int i = 0, imax = result.size(); i < imax; i++) {
+ //replace the path in the list with the appropriate resource type
+ IResource resource = resourceFor((IPath) result.get(i), files);
+
+ if (resource == null || ((Resource) resource).isFiltered() || (((memberFlags & IContainer.INCLUDE_HIDDEN) == 0) && resource.isHidden(IResource.CHECK_ANCESTORS)) || (((memberFlags & IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS) == 0) && resource.isTeamPrivateMember(IResource.CHECK_ANCESTORS)))
+ resource = null;
+
+ result.set(i, resource);
+ //count actual resources - some paths won't have a corresponding resource
+ if (resource != null)
+ count++;
+ }
+ //convert to array and remove null elements
+ IResource[] toReturn = files ? (IResource[]) new IFile[count] : (IResource[]) new IContainer[count];
+ count = 0;
+ for (Iterator it = result.iterator(); it.hasNext();) {
+ IResource resource = (IResource) it.next();
+ if (resource != null)
+ toReturn[count++] = resource;
+ }
+ return toReturn;
+ }
+
+ /* (non-javadoc)
+ * @see IResource.getResourceAttributes
+ */
+ public ResourceAttributes attributes(IResource resource) {
+ IFileStore store = getStore(resource);
+ IFileInfo fileInfo = store.fetchInfo();
+ if (!fileInfo.exists())
+ return null;
+ return FileUtil.fileInfoToAttributes(fileInfo);
+ }
+
+ /**
+ * Returns a container for the given file system location or null if there
+ * is no mapping for this path. If the path has only one segment, then an
+ * IProject is returned. Otherwise, the returned object
+ * is a IFolder. This method does NOT check the existence
+ * of a folder in the given location. Location cannot be null.
+ *
+ * The result will also omit resources that are explicitly excluded
+ * from the workspace according to existing resource filters. If all resources
+ * are omitted, the result may be null.
+ *
+ */
+ public IContainer containerForLocation(IPath location) {
+ return (IContainer) resourceForLocation(location, false);
+ }
+
+ /**
+ * Returns the resource corresponding to the given location. The
+ * "files" parameter is used for paths of two or more segments. If true,
+ * a file is returned, otherwise a folder is returned. Returns null if files is true
+ * and the path is not of sufficient length. Also returns null if the resource is
+ * filtered out by resource filters
+ */
+ private IResource resourceForLocation(IPath location, boolean files) {
+ if (workspace.getRoot().getLocation().equals(location)) {
+ if (!files)
+ return resourceFor(Path.ROOT, false);
+ return null;
+ }
+ IProject[] projects = getWorkspace().getRoot().getProjects(IContainer.INCLUDE_HIDDEN);
+ for (int i = 0; i < projects.length; i++) {
+ IProject project = projects[i];
+ IPath projectLocation = project.getLocation();
+ if (projectLocation != null && projectLocation.isPrefixOf(location)) {
+ int segmentsToRemove = projectLocation.segmentCount();
+ IPath path = project.getFullPath().append(location.removeFirstSegments(segmentsToRemove));
+ IResource resource = resourceFor(path, files);
+ if (resource != null && !((Resource) resource).isFiltered())
+ return resource;
+ }
+ }
+ return null;
+ }
+
+ public void copy(IResource target, IResource destination, int updateFlags, IProgressMonitor monitor) throws CoreException {
+ monitor = Policy.monitorFor(monitor);
+ try {
+ int totalWork = ((Resource) target).countResources(IResource.DEPTH_INFINITE, false);
+ String title = NLS.bind(Messages.localstore_copying, target.getFullPath());
+ monitor.beginTask(title, totalWork);
+ IFileStore destinationStore = getStore(destination);
+ if (destinationStore.fetchInfo().exists()) {
+ String message = NLS.bind(Messages.localstore_resourceExists, destination.getFullPath());
+ throw new ResourceException(IResourceStatus.FAILED_WRITE_LOCAL, destination.getFullPath(), message, null);
+ }
+ getHistoryStore().copyHistory(target, destination, false);
+ CopyVisitor visitor = new CopyVisitor(target, destination, updateFlags, monitor);
+ UnifiedTree tree = new UnifiedTree(target);
+ tree.accept(visitor, IResource.DEPTH_INFINITE);
+ IStatus status = visitor.getStatus();
+ if (!status.isOK())
+ throw new ResourceException(status);
+ } finally {
+ monitor.done();
+ }
+ }
+
+ public void delete(IResource target, int flags, IProgressMonitor monitor) throws CoreException {
+ monitor = Policy.monitorFor(monitor);
+ try {
+ Resource resource = (Resource) target;
+ final int deleteWork = resource.countResources(IResource.DEPTH_INFINITE, false) * 2;
+ boolean force = (flags & IResource.FORCE) != 0;
+ int refreshWork = 0;
+ if (!force)
+ refreshWork = Math.min(deleteWork, 100);
+ String title = NLS.bind(Messages.localstore_deleting, resource.getFullPath());
+ monitor.beginTask(title, deleteWork + refreshWork);
+ monitor.subTask(""); //$NON-NLS-1$
+ MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.FAILED_DELETE_LOCAL, Messages.localstore_deleteProblem, null);
+ List skipList = null;
+ UnifiedTree tree = new UnifiedTree(target);
+ if (!force) {
+ IProgressMonitor sub = Policy.subMonitorFor(monitor, refreshWork);
+ sub.beginTask("", 1000); //$NON-NLS-1$
+ try {
+ CollectSyncStatusVisitor refreshVisitor = new CollectSyncStatusVisitor(Messages.localstore_deleteProblem, sub);
+ refreshVisitor.setIgnoreLocalDeletions(true);
+ tree.accept(refreshVisitor, IResource.DEPTH_INFINITE);
+ status.merge(refreshVisitor.getSyncStatus());
+ skipList = refreshVisitor.getAffectedResources();
+ } finally {
+ sub.done();
+ }
+ }
+ DeleteVisitor deleteVisitor = new DeleteVisitor(skipList, flags, monitor, deleteWork);
+ tree.accept(deleteVisitor, IResource.DEPTH_INFINITE);
+ status.merge(deleteVisitor.getStatus());
+ if (!status.isOK())
+ throw new ResourceException(status);
+ } finally {
+ monitor.done();
+ }
+ }
+
+ /**
+ * Returns true if the description on disk is different from the given byte array,
+ * and false otherwise.
+ * Since org.eclipse.core.resources 3.4.1 differences in line endings (CR, LF, CRLF)
+ * are not considered.
+ */
+ private boolean descriptionChanged(IFile descriptionFile, byte[] newContents) {
+ InputStream oldStream = null;
+ try {
+ //buffer size: twice the description length, but maximum 8KB
+ int bufsize = newContents.length > 4096 ? 8192 : newContents.length * 2;
+ oldStream = new BufferedInputStream(descriptionFile.getContents(true), bufsize);
+ InputStream newStream = new ByteArrayInputStream(newContents);
+ //compare streams char by char, ignoring line endings
+ int newChar = newStream.read();
+ int oldChar = oldStream.read();
+ while (newChar >= 0 && oldChar >= 0) {
+ if (newChar == oldChar) {
+ //streams are the same
+ newChar = newStream.read();
+ oldChar = oldStream.read();
+ } else if ((newChar == '\r' || newChar == '\n') && (oldChar == '\r' || oldChar == '\n')) {
+ //got a difference, but both sides are newlines: read over newlines
+ while (newChar == '\r' || newChar == '\n')
+ newChar = newStream.read();
+ while (oldChar == '\r' || oldChar == '\n')
+ oldChar = oldStream.read();
+ } else {
+ //streams are different
+ return true;
+ }
+ }
+ //test for excess data in one stream
+ if (newChar >= 0 || oldChar >= 0)
+ return true;
+ return false;
+ } catch (Exception e) {
+ Policy.log(e);
+ //if we failed to compare, just write the new contents
+ } finally {
+ FileUtil.safeClose(oldStream);
+ }
+ return true;
+ }
+
+ /**
+ * @deprecated
+ */
+ @Deprecated
+ public int doGetEncoding(IFileStore store) throws CoreException {
+ InputStream input = null;
+ try {
+ input = store.openInputStream(EFS.NONE, null);
+ int first = input.read();
+ int second = input.read();
+ if (first == -1 || second == -1)
+ return IFile.ENCODING_UNKNOWN;
+ first &= 0xFF;//converts unsigned byte to int
+ second &= 0xFF;
+ //look for the UTF-16 Byte Order Mark (BOM)
+ if (first == 0xFE && second == 0xFF)
+ return IFile.ENCODING_UTF_16BE;
+ if (first == 0xFF && second == 0xFE)
+ return IFile.ENCODING_UTF_16LE;
+ int third = (input.read() & 0xFF);
+ if (third == -1)
+ return IFile.ENCODING_UNKNOWN;
+ //look for the UTF-8 BOM
+ if (first == 0xEF && second == 0xBB && third == 0xBF)
+ return IFile.ENCODING_UTF_8;
+ return IFile.ENCODING_UNKNOWN;
+ } catch (IOException e) {
+ String message = NLS.bind(Messages.localstore_couldNotRead, store.toString());
+ throw new ResourceException(IResourceStatus.FAILED_READ_LOCAL, null, message, e);
+ } finally {
+ FileUtil.safeClose(input);
+ }
+ }
+
+ /**
+ * Optimized sync check for files. Returns true if the file exists and is in sync, and false
+ * otherwise. The intent is to let the default implementation handle the complex
+ * cases like gender change, case variants, etc.
+ */
+ public boolean fastIsSynchronized(File target) {
+ ResourceInfo info = target.getResourceInfo(false, false);
+ if (target.exists(target.getFlags(info), true)) {
+ IFileInfo fileInfo = getStore(target).fetchInfo();
+ if (!fileInfo.isDirectory() && info.getLocalSyncInfo() == fileInfo.getLastModified())
+ return true;
+ }
+ return false;
+ }
+
+ public boolean fastIsSynchronized(Folder target) {
+ ResourceInfo info = target.getResourceInfo(false, false);
+ if (target.exists(target.getFlags(info), true)) {
+ IFileInfo fileInfo = getStore(target).fetchInfo();
+ if (!fileInfo.exists() && info.getLocalSyncInfo() == fileInfo.getLastModified())
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns an IFile for the given file system location or null if there
+ * is no mapping for this path. This method does NOT check the existence
+ * of a file in the given location. Location cannot be null.
+ *
+ * The result will also omit resources that are explicitly excluded
+ * from the workspace according to existing resource filters. If all resources
+ * are omitted, the result may be null.
+ *
+ */
+ public IFile fileForLocation(IPath location) {
+ return (IFile) resourceForLocation(location, true);
+ }
+
+ /**
+ * @deprecated
+ */
+ @Deprecated
+ public int getEncoding(File target) throws CoreException {
+ // thread safety: (the location can be null if the project for this file does not exist)
+ IFileStore store = getStore(target);
+ if (!store.fetchInfo().exists()) {
+ String message = NLS.bind(Messages.localstore_fileNotFound, store.toString());
+ throw new ResourceException(IResourceStatus.FAILED_READ_LOCAL, target.getFullPath(), message, null);
+ }
+ return doGetEncoding(store);
+ }
+
+ public IHistoryStore getHistoryStore() {
+ if (_historyStore == null) {
+ IPath location = getWorkspace().getMetaArea().getHistoryStoreLocation();
+ location.toFile().mkdirs();
+ IFileStore store = EFS.getLocalFileSystem().getStore(location);
+ _historyStore = new HistoryStore2(getWorkspace(), store, 256);
+ }
+ return _historyStore;
+ }
+
+ /**
+ * Returns the real name of the resource on disk. Returns null if no local
+ * file exists by that name. This is useful when dealing with
+ * case insensitive file systems.
+ */
+ public String getLocalName(IFileStore target) {
+ return target.fetchInfo().getName();
+ }
+
+ protected IPath getProjectDefaultLocation(IProject project) {
+ return workspace.getRoot().getLocation().append(project.getFullPath());
+ }
+
+ /**
+ * Never returns null
+ * @param target
+ * @return The file store for this resource
+ */
+ public IFileStore getStore(IResource target) {
+ try {
+ return getStoreRoot(target).createStore(target.getFullPath(), target);
+ } catch (CoreException e) {
+ //callers aren't expecting failure here, so return null file system
+ return EFS.getNullFileSystem().getStore(target.getFullPath());
+ }
+ }
+
+ /**
+ * Returns the file store root for the provided resource. Never returns null.
+ */
+ private FileStoreRoot getStoreRoot(IResource target) {
+ ResourceInfo info = workspace.getResourceInfo(target.getFullPath(), true, false);
+ FileStoreRoot root;
+ if (info != null) {
+ root = info.getFileStoreRoot();
+ if (root != null && root.isValid())
+ return root;
+ if (info.isSet(ICoreConstants.M_VIRTUAL)) {
+ ProjectDescription description = ((Project) target.getProject()).internalGetDescription();
+ if (description != null) {
+ setLocation(target, info, description.getGroupLocationURI(target.getProjectRelativePath()));
+ return info.getFileStoreRoot();
+ }
+ return info.getFileStoreRoot();
+ }
+ if (info.isSet(ICoreConstants.M_LINK)) {
+ ProjectDescription description = ((Project) target.getProject()).internalGetDescription();
+ if (description != null) {
+ final URI linkLocation = description.getLinkLocationURI(target.getProjectRelativePath());
+ //if we can't determine the link location, fall through to parent resource
+ if (linkLocation != null) {
+ setLocation(target, info, linkLocation);
+ return info.getFileStoreRoot();
+ }
+ }
+ }
+ }
+ final IContainer parent = target.getParent();
+ if (parent == null) {
+ //this is the root, so we know where this must be located
+ //initialize root location
+ info = workspace.getResourceInfo(Path.ROOT, false, true);
+ final IWorkspaceRoot rootResource = workspace.getRoot();
+ setLocation(rootResource, info, URIUtil.toURI(rootResource.getLocation()));
+ return info.getFileStoreRoot();
+ }
+ root = getStoreRoot(parent);
+ if (info != null)
+ info.setFileStoreRoot(root);
+ return root;
+ }
+
+ protected Workspace getWorkspace() {
+ return workspace;
+ }
+
+ /**
+ * Returns whether the project has any local content on disk.
+ */
+ public boolean hasSavedContent(IProject project) {
+ return getStore(project).fetchInfo().exists();
+ }
+
+ /**
+ * Returns whether the project has a project description file on disk.
+ */
+ public boolean hasSavedDescription(IProject project) {
+ return getStore(project).getChild(IProjectDescription.DESCRIPTION_FILE_NAME).fetchInfo().exists();
+ }
+
+ /**
+ * Initializes the file store for a resource.
+ *
+ * @param target The resource to initialize the file store for.
+ * @param location the File system location of this resource on disk
+ * @return The file store for the provided resource
+ */
+ private IFileStore initializeStore(IResource target, URI location) throws CoreException {
+ ResourceInfo info = ((Resource) target).getResourceInfo(false, true);
+ setLocation(target, info, location);
+ FileStoreRoot root = getStoreRoot(target);
+ return root.createStore(target.getFullPath(), target);
+ }
+
+ /**
+ * The target must exist in the workspace. This method must only ever
+ * be called from Project.writeDescription(), because that method ensures
+ * that the description isn't then immediately discovered as a new change.
+ * @return true if a new description was written, and false if it wasn't written
+ * because it was unchanged
+ */
+ public boolean internalWrite(IProject target, IProjectDescription description, int updateFlags, boolean hasPublicChanges, boolean hasPrivateChanges) throws CoreException {
+ //write the project's private description to the metadata area
+ if (hasPrivateChanges)
+ getWorkspace().getMetaArea().writePrivateDescription(target);
+ if (!hasPublicChanges)
+ return false;
+ //can't do anything if there's no description
+ if (description == null)
+ return false;
+
+ //write the model to a byte array
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ IFile descriptionFile = target.getFile(IProjectDescription.DESCRIPTION_FILE_NAME);
+ try {
+ new ModelObjectWriter().write(description, out, FileUtil.getLineSeparator(descriptionFile));
+ } catch (IOException e) {
+ String msg = NLS.bind(Messages.resources_writeMeta, target.getFullPath());
+ throw new ResourceException(IResourceStatus.FAILED_WRITE_METADATA, target.getFullPath(), msg, e);
+ }
+ byte[] newContents = out.toByteArray();
+
+ //write the contents to the IFile that represents the description
+ if (!descriptionFile.exists())
+ workspace.createResource(descriptionFile, false);
+ else {
+ //if the description has not changed, don't write anything
+ if (!descriptionChanged(descriptionFile, newContents))
+ return false;
+ }
+ ByteArrayInputStream in = new ByteArrayInputStream(newContents);
+ IFileStore descriptionFileStore = ((Resource) descriptionFile).getStore();
+ IFileInfo fileInfo = descriptionFileStore.fetchInfo();
+ if (fileInfo.getAttribute(EFS.ATTRIBUTE_READ_ONLY)) {
+ IStatus result = getWorkspace().validateEdit(new IFile[] {descriptionFile}, null);
+ if (!result.isOK())
+ throw new ResourceException(result);
+ // re-read the file info in case the file attributes were modified
+ fileInfo = descriptionFileStore.fetchInfo();
+ }
+ //write the project description file (don't use API because scheduling rule might not match)
+ write(descriptionFile, in, fileInfo, IResource.FORCE, false, Policy.monitorFor(null));
+ workspace.getAliasManager().updateAliases(descriptionFile, getStore(descriptionFile), IResource.DEPTH_ZERO, Policy.monitorFor(null));
+
+ //update the timestamp on the project as well so we know when it has
+ //been changed from the outside
+ long lastModified = ((Resource) descriptionFile).getResourceInfo(false, false).getLocalSyncInfo();
+ ResourceInfo info = ((Resource) target).getResourceInfo(false, true);
+ updateLocalSync(info, lastModified);
+
+ //for backwards compatibility, ensure the old .prj file is deleted
+ getWorkspace().getMetaArea().clearOldDescription(target);
+ return true;
+ }
+
+ /**
+ * Returns true if the given project's description is synchronized with
+ * the project description file on disk, and false otherwise.
+ */
+ public boolean isDescriptionSynchronized(IProject target) {
+ //sync info is stored on the description file, and on project info.
+ //when the file is changed by someone else, the project info modification
+ //stamp will be out of date
+ IFile descriptionFile = target.getFile(IProjectDescription.DESCRIPTION_FILE_NAME);
+ ResourceInfo projectInfo = ((Resource) target).getResourceInfo(false, false);
+ if (projectInfo == null)
+ return false;
+ return projectInfo.getLocalSyncInfo() == getStore(descriptionFile).fetchInfo().getLastModified();
+ }
+
+ /**
+ * Returns true if the given resource is synchronized with the file system
+ * to the given depth. Returns false otherwise.
+ *
+ * Any discovered out-of-sync resources are scheduled to be brought
+ * back in sync, if {@link ResourcesPlugin#PREF_LIGHTWEIGHT_AUTO_REFRESH} is
+ * enabled.
+ *
+ * @see IResource#isSynchronized(int)
+ */
+ public boolean isSynchronized(IResource target, int depth) {
+ switch (target.getType()) {
+ case IResource.ROOT :
+ if (depth == IResource.DEPTH_ZERO)
+ return true;
+ //check sync on child projects.
+ depth = depth == IResource.DEPTH_ONE ? IResource.DEPTH_ZERO : depth;
+ IProject[] projects = ((IWorkspaceRoot) target).getProjects(IContainer.INCLUDE_HIDDEN);
+ for (int i = 0; i < projects.length; i++) {
+ if (!isSynchronized(projects[i], depth))
+ return false;
+ }
+ return true;
+ case IResource.PROJECT :
+ if (!target.isAccessible())
+ return true;
+ break;
+ case IResource.FOLDER :
+ if (fastIsSynchronized((Folder) target))
+ return true;
+ break;
+ case IResource.FILE :
+ if (fastIsSynchronized((File) target))
+ return true;
+ break;
+ }
+ IsSynchronizedVisitor visitor = new IsSynchronizedVisitor(Policy.monitorFor(null));
+ UnifiedTree tree = new UnifiedTree(target);
+ try {
+ tree.accept(visitor, depth);
+ } catch (CoreException e) {
+ Policy.log(e);
+ return false;
+ } catch (IsSynchronizedVisitor.ResourceChangedException e) {
+ // Ask refresh manager to bring out-of-sync resource back into sync when convenient
+ asyncRefresh(e.target);
+ //visitor throws an exception if out of sync
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Check whether the preference {@link ResourcesPlugin#PREF_LIGHTWEIGHT_AUTO_REFRESH} is
+ * enabled. When this preference is true the Resources plugin automatically refreshes
+ * resources which are known to be out-of-sync, and may install lightweight filesystem
+ * notification hooks.
+ * @return whether this FSRM is automatically refreshing discovered out-of-sync resources
+ */
+ public boolean isLightweightAutoRefreshEnabled() {
+ return lightweightAutoRefreshEnabled;
+ }
+
+ public void link(Resource target, URI location, IFileInfo fileInfo) throws CoreException {
+ initializeStore(target, location);
+ ResourceInfo info = target.getResourceInfo(false, true);
+ long lastModified = fileInfo == null ? 0 : fileInfo.getLastModified();
+ if (lastModified == 0)
+ info.clearModificationStamp();
+ updateLocalSync(info, lastModified);
+ }
+
+ /**
+ * Returns the resolved, absolute file system location of the given resource.
+ * Returns null if the location could not be resolved. No canonicalization is
+ * applied to the returned path.
+ *
+ * @param target the resource to get the location for
+ */
+ public IPath locationFor(IResource target) {
+ return locationFor(target, false);
+ }
+
+ /**
+ * Returns the resolved, absolute file system location of the given resource.
+ * Returns null if the location could not be resolved.
+ *
+ * @param target the resource to get the location for
+ * @param canonical if {@code true}, the prefix of the returned path corresponding
+ * to the resource's file store root will be canonicalized
+ */
+ public IPath locationFor(IResource target, boolean canonical) {
+ return getStoreRoot(target).localLocation(target.getFullPath(), target, false);
+ }
+
+ /**
+ * Returns the resolved, absolute file system location of the given resource.
+ * Returns null if the location could not be resolved. No canonicalization is
+ * applied to the returned URI.
+ *
+ * @param target the resource to get the location URI for
+ */
+ public URI locationURIFor(IResource target) {
+ return locationURIFor(target, false);
+ }
+
+ /**
+ * Returns the resolved, absolute file system location of the given resource.
+ * Returns null if the location could not be resolved.
+ *
+ * @param target the resource to get the location URI for
+ * @param canonical if {@code true}, the prefix of the path of the returned URI
+ * corresponding to resource's file store root will be canonicalized
+ */
+ public URI locationURIFor(IResource target, boolean canonical) {
+ return getStoreRoot(target).computeURI(target.getFullPath(), canonical);
+ }
+
+ public void move(IResource source, IFileStore destination, int flags, IProgressMonitor monitor) throws CoreException {
+ //TODO figure out correct semantics for case where destination exists on disk
+ getStore(source).move(destination, EFS.NONE, monitor);
+ }
+
+ @Deprecated
+ @Override
+ public void propertyChange(PropertyChangeEvent event) {
+ if (ResourcesPlugin.PREF_LIGHTWEIGHT_AUTO_REFRESH.equals(event.getProperty()))
+ lightweightAutoRefreshEnabled = Boolean.valueOf(event.getNewValue().toString());
+ }
+
+ public InputStream read(IFile target, boolean force, IProgressMonitor monitor) throws CoreException {
+ IFileStore store = getStore(target);
+ if (lightweightAutoRefreshEnabled || !force) {
+ final IFileInfo fileInfo = store.fetchInfo();
+ if (!fileInfo.exists()) {
+ asyncRefresh(target);
+ if (!force) {
+ String message = NLS.bind(Messages.localstore_fileNotFound, store.toString());
+ throw new ResourceException(IResourceStatus.FAILED_READ_LOCAL, target.getFullPath(), message, null);
+ }
+ }
+ ResourceInfo info = ((Resource) target).getResourceInfo(true, false);
+ int flags = ((Resource) target).getFlags(info);
+ ((Resource) target).checkExists(flags, true);
+ if (fileInfo.getLastModified() != info.getLocalSyncInfo()) {
+ asyncRefresh(target);
+ if (!force) {
+ String message = NLS.bind(Messages.localstore_resourceIsOutOfSync, target.getFullPath());
+ throw new ResourceException(IResourceStatus.OUT_OF_SYNC_LOCAL, target.getFullPath(), message, null);
+ }
+ }
+ }
+ return store.openInputStream(EFS.NONE, monitor);
+ }
+
+ /**
+ * Reads and returns the project description for the given project.
+ * Never returns null.
+ * @param target the project whose description should be read.
+ * @param creation true if this project is just being created, in which
+ * case the private project information (including the location) needs to be read
+ * from disk as well.
+ * @exception CoreException if there was any failure to read the project
+ * description, or if the description was missing.
+ */
+ public ProjectDescription read(IProject target, boolean creation) throws CoreException {
+ IProgressMonitor monitor = Policy.monitorFor(null);
+
+ //read the project location if this project is being created
+ URI projectLocation = null;
+ ProjectDescription privateDescription = null;
+ if (creation) {
+ privateDescription = new ProjectDescription();
+ getWorkspace().getMetaArea().readPrivateDescription(target, privateDescription);
+ projectLocation = privateDescription.getLocationURI();
+ } else {
+ IProjectDescription description = ((Project) target).internalGetDescription();
+ if (description != null && description.getLocationURI() != null) {
+ projectLocation = description.getLocationURI();
+ }
+ }
+ final boolean isDefaultLocation = projectLocation == null;
+ if (isDefaultLocation) {
+ projectLocation = URIUtil.toURI(getProjectDefaultLocation(target));
+ }
+ IFileStore projectStore = initializeStore(target, projectLocation);
+ IFileStore descriptionStore = projectStore.getChild(IProjectDescription.DESCRIPTION_FILE_NAME);
+ ProjectDescription description = null;
+ //hold onto any exceptions until after sync info is updated, then throw it
+ ResourceException error = null;
+ InputStream in = null;
+ try {
+ in = new BufferedInputStream(descriptionStore.openInputStream(EFS.NONE, monitor));
+ // IFileStore#openInputStream may cancel the monitor, thus the monitor state is checked
+ Policy.checkCanceled(monitor);
+ description = new ProjectDescriptionReader(target).read(new InputSource(in));
+ } catch (OperationCanceledException e) {
+ String msg = NLS.bind(Messages.resources_missingProjectMeta, target.getName());
+ throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, target.getFullPath(), msg, e);
+ } catch (CoreException e) {
+ //try the legacy location in the meta area
+ description = getWorkspace().getMetaArea().readOldDescription(target);
+ if (description != null)
+ return description;
+ if (!descriptionStore.fetchInfo().exists()) {
+ String msg = NLS.bind(Messages.resources_missingProjectMeta, target.getName());
+ throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, target.getFullPath(), msg, null);
+ }
+ String msg = NLS.bind(Messages.resources_readProjectMeta, target.getName());
+ error = new ResourceException(IResourceStatus.FAILED_READ_METADATA, target.getFullPath(), msg, e);
+ } finally {
+ FileUtil.safeClose(in);
+ }
+ if (error == null && description == null) {
+ String msg = NLS.bind(Messages.resources_readProjectMeta, target.getName());
+ error = new ResourceException(IResourceStatus.FAILED_READ_METADATA, target.getFullPath(), msg, null);
+ }
+ if (description != null) {
+ if (!isDefaultLocation)
+ description.setLocationURI(projectLocation);
+ if (creation && privateDescription != null)
+ // Bring dynamic state back to life
+ description.updateDynamicState(privateDescription);
+ }
+ long lastModified = descriptionStore.fetchInfo().getLastModified();
+ IFile descriptionFile = target.getFile(IProjectDescription.DESCRIPTION_FILE_NAME);
+ //don't get a mutable copy because we might be in restore which isn't an operation
+ //it doesn't matter anyway because local sync info is not included in deltas
+ ResourceInfo info = ((Resource) descriptionFile).getResourceInfo(false, false);
+ if (info == null) {
+ //create a new resource on the sly -- don't want to start an operation
+ info = getWorkspace().createResource(descriptionFile, false);
+ updateLocalSync(info, lastModified);
+ }
+ //if the project description has changed between sessions, let it remain
+ //out of sync -- that way link changes will be reconciled on next refresh
+ if (!creation)
+ updateLocalSync(info, lastModified);
+
+ //update the timestamp on the project as well so we know when it has
+ //been changed from the outside
+ info = ((Resource) target).getResourceInfo(false, true);
+ updateLocalSync(info, lastModified);
+
+ if (error != null)
+ throw error;
+ return description;
+ }
+
+ public boolean refresh(IResource target, int depth, boolean updateAliases, IProgressMonitor monitor) throws CoreException {
+ switch (target.getType()) {
+ case IResource.ROOT :
+ return refreshRoot((IWorkspaceRoot) target, depth, updateAliases, monitor);
+ case IResource.PROJECT :
+ if (!target.isAccessible())
+ return false;
+ //fall through
+ case IResource.FOLDER :
+ case IResource.FILE :
+ return refreshResource(target, depth, updateAliases, monitor);
+ }
+ return false;
+ }
+
+ protected boolean refreshResource(IResource target, int depth, boolean updateAliases, IProgressMonitor monitor) throws CoreException {
+ monitor = Policy.monitorFor(monitor);
+ int totalWork = RefreshLocalVisitor.TOTAL_WORK;
+ String title = NLS.bind(Messages.localstore_refreshing, target.getFullPath());
+ try {
+ monitor.beginTask(title, totalWork);
+ RefreshLocalVisitor visitor = updateAliases ? new RefreshLocalAliasVisitor(monitor) : new RefreshLocalVisitor(monitor);
+ IFileStore fileStore = ((Resource) target).getStore();
+ //try to get all info in one shot, if file system supports it
+ IFileTree fileTree = fileStore.getFileSystem().fetchFileTree(fileStore, new SubProgressMonitor(monitor, 0));
+ UnifiedTree tree = fileTree == null ? new UnifiedTree(target) : new UnifiedTree(target, fileTree);
+ tree.accept(visitor, depth);
+ IStatus result = visitor.getErrorStatus();
+ if (!result.isOK())
+ throw new ResourceException(result);
+ return visitor.resourcesChanged();
+ } finally {
+ monitor.done();
+ }
+ }
+
+ /**
+ * Synchronizes the entire workspace with the local file system.
+ * The current implementation does this by synchronizing each of the
+ * projects currently in the workspace. A better implementation may
+ * be possible.
+ */
+ protected boolean refreshRoot(IWorkspaceRoot target, int depth, boolean updateAliases, IProgressMonitor monitor) throws CoreException {
+ monitor = Policy.monitorFor(monitor);
+ IProject[] projects = target.getProjects(IContainer.INCLUDE_HIDDEN);
+ int totalWork = projects.length;
+ String title = Messages.localstore_refreshingRoot;
+ try {
+ monitor.beginTask(title, totalWork);
+ // if doing depth zero, there is nothing to do (can't refresh the root).
+ // Note that we still need to do the beginTask, done pair.
+ if (depth == IResource.DEPTH_ZERO)
+ return false;
+ boolean changed = false;
+ // drop the depth by one level since processing the root counts as one level.
+ depth = depth == IResource.DEPTH_ONE ? IResource.DEPTH_ZERO : depth;
+ for (int i = 0; i < projects.length; i++)
+ changed |= refresh(projects[i], depth, updateAliases, Policy.subMonitorFor(monitor, 1));
+ return changed;
+ } finally {
+ monitor.done();
+ }
+ }
+
+ /**
+ * Returns the resource corresponding to the given workspace path. The
+ * "files" parameter is used for paths of two or more segments. If true,
+ * a file is returned, otherwise a folder is returned. Returns null if files is true
+ * and the path is not of sufficient length.
+ */
+ protected IResource resourceFor(IPath path, boolean files) {
+ int numSegments = path.segmentCount();
+ if (files && numSegments < ICoreConstants.MINIMUM_FILE_SEGMENT_LENGTH)
+ return null;
+ IWorkspaceRoot root = getWorkspace().getRoot();
+ if (path.isRoot())
+ return root;
+ if (numSegments == 1)
+ return root.getProject(path.segment(0));
+ return files ? (IResource) root.getFile(path) : (IResource) root.getFolder(path);
+ }
+
+ /* (non-javadoc)
+ * @see IResouce.setLocalTimeStamp
+ */
+ public long setLocalTimeStamp(IResource target, ResourceInfo info, long value) throws CoreException {
+ IFileStore store = getStore(target);
+ IFileInfo fileInfo = store.fetchInfo();
+ fileInfo.setLastModified(value);
+ store.putInfo(fileInfo, EFS.SET_LAST_MODIFIED, null);
+ //actual value may be different depending on file system granularity
+ fileInfo = store.fetchInfo();
+ long actualValue = fileInfo.getLastModified();
+ updateLocalSync(info, actualValue);
+ return actualValue;
+ }
+
+ /**
+ * The storage location for a resource has changed; update the location.
+ * @param target
+ * @param info
+ * @param location
+ */
+ public void setLocation(IResource target, ResourceInfo info, URI location) {
+ FileStoreRoot oldRoot = info.getFileStoreRoot();
+ if (location != null) {
+ location = FileUtil.realURI(location); // Normalize case as it exists on the file system.
+ info.setFileStoreRoot(new FileStoreRoot(location, target.getFullPath()));
+ } else {
+ //project is in default location so clear the store root
+ info.setFileStoreRoot(null);
+ }
+ if (oldRoot != null)
+ oldRoot.setValid(false);
+ }
+
+ /* (non-javadoc)
+ * @see IResource.setResourceAttributes
+ */
+ public void setResourceAttributes(IResource resource, ResourceAttributes attributes) throws CoreException {
+ IFileStore store = getStore(resource);
+ //when the executable bit is changed on a folder a refresh is required
+ boolean refresh = false;
+ if (resource instanceof IContainer && ((store.getFileSystem().attributes() & EFS.ATTRIBUTE_EXECUTABLE) != 0))
+ refresh = store.fetchInfo().getAttribute(EFS.ATTRIBUTE_EXECUTABLE) != attributes.isExecutable();
+ store.putInfo(FileUtil.attributesToFileInfo(attributes), EFS.SET_ATTRIBUTES, null);
+ //must refresh in the background because we are not inside an operation
+ if (refresh)
+ workspace.getRefreshManager().refresh(resource);
+ }
+
+ @Override
+ public void shutdown(IProgressMonitor monitor) throws CoreException {
+ if (_historyStore != null)
+ _historyStore.shutdown(monitor);
+ ResourcesPlugin.getPlugin().getPluginPreferences().removePropertyChangeListener(this);
+ }
+
+ @Override
+ public void startup(IProgressMonitor monitor) {
+ Preferences preferences = ResourcesPlugin.getPlugin().getPluginPreferences();
+ preferences.addPropertyChangeListener(this);
+ lightweightAutoRefreshEnabled = preferences.getBoolean(ResourcesPlugin.PREF_LIGHTWEIGHT_AUTO_REFRESH);
+ }
+
+ /**
+ * The ResourceInfo must be mutable.
+ */
+ public void updateLocalSync(ResourceInfo info, long localSyncInfo) {
+ info.setLocalSyncInfo(localSyncInfo);
+ if (localSyncInfo == I_NULL_SYNC_INFO)
+ info.clear(M_LOCAL_EXISTS);
+ else
+ info.set(M_LOCAL_EXISTS);
+ }
+
+ /**
+ * The target must exist in the workspace. The content InputStream is
+ * closed even if the method fails. If the force flag is false we only write
+ * the file if it does not exist or if it is already local and the timestamp
+ * has NOT changed since last synchronization, otherwise a CoreException
+ * is thrown.
+ */
+ public void write(IFile target, InputStream content, IFileInfo fileInfo, int updateFlags, boolean append, IProgressMonitor monitor) throws CoreException {
+ monitor = Policy.monitorFor(null);
+ try {
+ IFileStore store = getStore(target);
+ if (fileInfo.getAttribute(EFS.ATTRIBUTE_READ_ONLY)) {
+ String message = NLS.bind(Messages.localstore_couldNotWriteReadOnly, target.getFullPath());
+ throw new ResourceException(IResourceStatus.FAILED_WRITE_LOCAL, target.getFullPath(), message, null);
+ }
+ long lastModified = fileInfo.getLastModified();
+ if (BitMask.isSet(updateFlags, IResource.FORCE)) {
+ if (append && !target.isLocal(IResource.DEPTH_ZERO) && !fileInfo.exists()) {
+ // force=true, local=false, existsInFileSystem=false
+ String message = NLS.bind(Messages.resources_mustBeLocal, target.getFullPath());
+ throw new ResourceException(IResourceStatus.RESOURCE_NOT_LOCAL, target.getFullPath(), message, null);
+ }
+ } else {
+ if (target.isLocal(IResource.DEPTH_ZERO)) {
+ ResourceInfo info = ((Resource) target).getResourceInfo(true, false);
+ // test if timestamp is the same since last synchronization
+ if (lastModified != info.getLocalSyncInfo()) {
+ asyncRefresh(target);
+ String message = NLS.bind(Messages.localstore_resourceIsOutOfSync, target.getFullPath());
+ throw new ResourceException(IResourceStatus.OUT_OF_SYNC_LOCAL, target.getFullPath(), message, null);
+ }
+ if (!fileInfo.exists()) {
+ asyncRefresh(target);
+ String message = NLS.bind(Messages.localstore_resourceDoesNotExist, target.getFullPath());
+ throw new ResourceException(IResourceStatus.NOT_FOUND_LOCAL, target.getFullPath(), message, null);
+ }
+ } else {
+ if (fileInfo.exists()) {
+ String message = NLS.bind(Messages.localstore_resourceExists, target.getFullPath());
+ throw new ResourceException(IResourceStatus.EXISTS_LOCAL, target.getFullPath(), message, null);
+ }
+ if (append) {
+ String message = NLS.bind(Messages.resources_mustBeLocal, target.getFullPath());
+ throw new ResourceException(IResourceStatus.RESOURCE_NOT_LOCAL, target.getFullPath(), message, null);
+ }
+ }
+ }
+ // add entry to History Store.
+ if (BitMask.isSet(updateFlags, IResource.KEEP_HISTORY) && fileInfo.exists())
+ //never move to the history store, because then the file is missing if write fails
+ getHistoryStore().addState(target.getFullPath(), store, fileInfo, false);
+ if (!fileInfo.exists())
+ store.getParent().mkdir(EFS.NONE, null);
+ int options = append ? EFS.APPEND : EFS.NONE;
+ OutputStream out = store.openOutputStream(options, Policy.subMonitorFor(monitor, 0));
+ FileUtil.transferStreams(content, out, store.toString(), monitor);
+ // get the new last modified time and stash in the info
+ lastModified = store.fetchInfo().getLastModified();
+ ResourceInfo info = ((Resource) target).getResourceInfo(false, true);
+ updateLocalSync(info, lastModified);
+ info.incrementContentId();
+ info.clear(M_CONTENT_CACHE);
+ workspace.updateModificationStamp(info);
+ } finally {
+ FileUtil.safeClose(content);
+ }
+ }
+
+ /**
+ * If force is false, this method fails if there is already a resource in
+ * target's location.
+ */
+ public void write(IFolder target, boolean force, IProgressMonitor monitor) throws CoreException {
+ IFileStore store = getStore(target);
+ if (!force) {
+ IFileInfo fileInfo = store.fetchInfo();
+ if (fileInfo.isDirectory()) {
+ String message = NLS.bind(Messages.localstore_resourceExists, target.getFullPath());
+ throw new ResourceException(IResourceStatus.EXISTS_LOCAL, target.getFullPath(), message, null);
+ }
+ if (fileInfo.exists()) {
+ String message = NLS.bind(Messages.localstore_fileExists, target.getFullPath());
+ throw new ResourceException(IResourceStatus.OUT_OF_SYNC_LOCAL, target.getFullPath(), message, null);
+ }
+ }
+ store.mkdir(EFS.NONE, monitor);
+ ResourceInfo info = ((Resource) target).getResourceInfo(false, true);
+ updateLocalSync(info, store.fetchInfo().getLastModified());
+ }
+
+ /**
+ * Write the .project file without modifying the resource tree. This is called
+ * during save when it is discovered that the .project file is missing. The tree
+ * cannot be modified during save.
+ */
+ public void writeSilently(IProject target) throws CoreException {
+ IPath location = locationFor(target, false);
+ //if the project location cannot be resolved, we don't know if a description file exists or not
+ if (location == null)
+ return;
+ IFileStore projectStore = getStore(target);
+ projectStore.mkdir(EFS.NONE, null);
+ //can't do anything if there's no description
+ IProjectDescription desc = ((Project) target).internalGetDescription();
+ if (desc == null)
+ return;
+ //write the project's private description to the meta-data area
+ getWorkspace().getMetaArea().writePrivateDescription(target);
+
+ //write the file that represents the project description
+ IFileStore fileStore = projectStore.getChild(IProjectDescription.DESCRIPTION_FILE_NAME);
+ OutputStream out = null;
+ try {
+ out = fileStore.openOutputStream(EFS.NONE, null);
+ IFile file = target.getFile(IProjectDescription.DESCRIPTION_FILE_NAME);
+ new ModelObjectWriter().write(desc, out, FileUtil.getLineSeparator(file));
+ out.close();
+ } catch (IOException e) {
+ String msg = NLS.bind(Messages.resources_writeMeta, target.getFullPath());
+ throw new ResourceException(IResourceStatus.FAILED_WRITE_METADATA, target.getFullPath(), msg, e);
+ } finally {
+ FileUtil.safeClose(out);
+ }
+ //for backwards compatibility, ensure the old .prj file is deleted
+ getWorkspace().getMetaArea().clearOldDescription(target);
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/HistoryBucket.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/HistoryBucket.java
new file mode 100644
index 0000000000..586b25d0b3
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/HistoryBucket.java
@@ -0,0 +1,326 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2015 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * James Blackburn (Broadcom Corp.) - ongoing development
+ *******************************************************************************/
+package org.eclipse.core.internal.localstore;
+
+import java.io.*;
+import java.util.Arrays;
+import java.util.Comparator;
+import org.eclipse.core.internal.utils.UniversalUniqueIdentifier;
+import org.eclipse.core.runtime.IPath;
+
+public class HistoryBucket extends Bucket {
+
+ /**
+ * A entry in the bucket index. Each entry has one path and a collection
+ * of states, which by their turn contain a (UUID, timestamp) pair.
+ *
+ * This class is intended as a lightweight way of hiding the internal data structure.
+ * Objects of this class are supposed to be short-lived. No instances
+ * of this class are kept stored anywhere. The real stuff (the internal data structure)
+ * is.
+ *
+ */
+ public static final class HistoryEntry extends Bucket.Entry {
+
+ final static Comparator COMPARATOR = new Comparator() {
+ @Override
+ public int compare(byte[] state1, byte[] state2) {
+ return compareStates(state1, state2);
+ }
+ };
+
+ // the length of each component of the data array
+ private final static byte[][] EMPTY_DATA = new byte[0][];
+ // the length of a long in bytes
+ private final static int LONG_LENGTH = 8;
+ // the length of a UUID in bytes
+ private final static int UUID_LENGTH = UniversalUniqueIdentifier.BYTES_SIZE;
+ public final static int DATA_LENGTH = UUID_LENGTH + LONG_LENGTH;
+
+ /**
+ * The history states. The first array dimension is the number of states. The
+ * second dimension is an encoding of the {UUID,timestamp} pair for that entry.
+ */
+ private byte[][] data;
+
+ /**
+ * Comparison logic for states in byte[] form.
+ *
+ * @see Comparator#compare(java.lang.Object, java.lang.Object)
+ */
+ static int compareStates(byte[] state1, byte[] state2) {
+ long timestamp1 = getTimestamp(state1);
+ long timestamp2 = getTimestamp(state2);
+ if (timestamp1 == timestamp2)
+ return -UniversalUniqueIdentifier.compareTime(state1, state2);
+ return timestamp1 < timestamp2 ? +1 : -1;
+ }
+
+ /**
+ * Returns the byte array representation of a (UUID, timestamp) pair.
+ */
+ static byte[] getState(UniversalUniqueIdentifier uuid, long timestamp) {
+ byte[] uuidBytes = uuid.toBytes();
+ byte[] state = new byte[DATA_LENGTH];
+ System.arraycopy(uuidBytes, 0, state, 0, uuidBytes.length);
+ for (int j = 0; j < LONG_LENGTH; j++) {
+ state[UUID_LENGTH + j] = (byte) (0xFF & timestamp);
+ timestamp >>>= 8;
+ }
+ return state;
+ }
+
+ private static long getTimestamp(byte[] state) {
+ long timestamp = 0;
+ for (int j = 0; j < LONG_LENGTH; j++)
+ timestamp += (state[UUID_LENGTH + j] & 0xFFL) << j * 8;
+ return timestamp;
+ }
+
+ /**
+ * Inserts the given item into the given array at the right position.
+ * Returns the resulting array. Returns null if the item already exists.
+ */
+ static byte[][] insert(byte[][] existing, byte[] toAdd) {
+ // look for the right spot where to insert the new guy
+ int index = search(existing, toAdd);
+ if (index >= 0)
+ // already there - nothing else to be done
+ return null;
+ // not found - insert
+ int insertPosition = -index - 1;
+ byte[][] newValue = new byte[existing.length + 1][];
+ if (insertPosition > 0)
+ System.arraycopy(existing, 0, newValue, 0, insertPosition);
+ newValue[insertPosition] = toAdd;
+ if (insertPosition < existing.length)
+ System.arraycopy(existing, insertPosition, newValue, insertPosition + 1, existing.length - insertPosition);
+ return newValue;
+ }
+
+ /**
+ * Merges two entries (are always sorted). Duplicates are discarded.
+ */
+ static byte[][] merge(byte[][] base, byte[][] additions) {
+ int additionPointer = 0;
+ int basePointer = 0;
+ int added = 0;
+ byte[][] result = new byte[base.length + additions.length][];
+ while (basePointer < base.length && additionPointer < additions.length) {
+ int comparison = compareStates(base[basePointer], additions[additionPointer]);
+ if (comparison == 0) {
+ result[added++] = base[basePointer++];
+ // duplicate, ignore
+ additionPointer++;
+ } else if (comparison < 0)
+ result[added++] = base[basePointer++];
+ else
+ result[added++] = additions[additionPointer++];
+ }
+ // copy the remaining states from either additions or base arrays
+ byte[][] remaining = basePointer == base.length ? additions : base;
+ int remainingPointer = basePointer == base.length ? additionPointer : basePointer;
+ int remainingCount = remaining.length - remainingPointer;
+ System.arraycopy(remaining, remainingPointer, result, added, remainingCount);
+ added += remainingCount;
+ if (added == base.length + additions.length)
+ // no collisions
+ return result;
+ // there were collisions, need to compact
+ byte[][] finalResult = new byte[added][];
+ System.arraycopy(result, 0, finalResult, 0, finalResult.length);
+ return finalResult;
+ }
+
+ private static int search(byte[][] existing, byte[] element) {
+ return Arrays.binarySearch(existing, element, COMPARATOR);
+ }
+
+ public HistoryEntry(IPath path, byte[][] data) {
+ super(path);
+ this.data = data;
+ }
+
+ public HistoryEntry(IPath path, HistoryEntry base) {
+ super(path);
+ this.data = new byte[base.data.length][];
+ System.arraycopy(base.data, 0, this.data, 0, this.data.length);
+ }
+
+ /**
+ * Compacts the data array removing any null slots. If non-null slots
+ * are found, the entry is marked for removal.
+ */
+ private void compact() {
+ if (!isDirty())
+ return;
+ int occurrences = 0;
+ for (int i = 0; i < data.length; i++)
+ if (data[i] != null)
+ data[occurrences++] = data[i];
+ if (occurrences == data.length)
+ // no states deleted
+ return;
+ if (occurrences == 0) {
+ // no states remaining
+ data = EMPTY_DATA;
+ delete();
+ return;
+ }
+ byte[][] result = new byte[occurrences][];
+ System.arraycopy(data, 0, result, 0, occurrences);
+ data = result;
+ }
+
+ public void deleteOccurrence(int i) {
+ markDirty();
+ data[i] = null;
+ }
+
+ byte[][] getData() {
+ return data;
+ }
+
+ @Override
+ public int getOccurrences() {
+ return data.length;
+ }
+
+ public long getTimestamp(int i) {
+ return getTimestamp(data[i]);
+ }
+
+ public UniversalUniqueIdentifier getUUID(int i) {
+ return new UniversalUniqueIdentifier(data[i]);
+ }
+
+ @Override
+ public Object getValue() {
+ return data;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return data.length == 0;
+ }
+
+ @Override
+ public void visited() {
+ compact();
+ }
+
+ }
+
+ /**
+ * Version number for the current implementation file's format.
+ *
+ */
+ public final static byte VERSION = 2;
+
+ public HistoryBucket() {
+ super();
+ }
+
+ public void addBlob(IPath path, UniversalUniqueIdentifier uuid, long lastModified) {
+ byte[] state = HistoryEntry.getState(uuid, lastModified);
+ String pathAsString = path.toString();
+ byte[][] existing = (byte[][]) getEntryValue(pathAsString);
+ if (existing == null) {
+ setEntryValue(pathAsString, new byte[][] {state});
+ return;
+ }
+ byte[][] newValue = HistoryEntry.insert(existing, state);
+ if (newValue == null)
+ return;
+ setEntryValue(pathAsString, newValue);
+ }
+
+ public void addBlobs(HistoryEntry fileEntry) {
+ IPath path = fileEntry.getPath();
+ byte[][] additions = fileEntry.getData();
+ String pathAsString = path.toString();
+ byte[][] existing = (byte[][]) getEntryValue(pathAsString);
+ if (existing == null) {
+ setEntryValue(pathAsString, additions);
+ return;
+ }
+ setEntryValue(pathAsString, HistoryEntry.merge(existing, additions));
+ }
+
+ @Override
+ protected Bucket.Entry createEntry(IPath path, Object value) {
+ return new HistoryEntry(path, (byte[][]) value);
+ }
+
+ public HistoryEntry getEntry(IPath path) {
+ String pathAsString = path.toString();
+ byte[][] existing = (byte[][]) getEntryValue(pathAsString);
+ if (existing == null)
+ return null;
+ return new HistoryEntry(path, existing);
+ }
+
+ @Override
+ protected String getIndexFileName() {
+ return "history.index"; //$NON-NLS-1$
+ }
+
+ @Override
+ protected byte getVersion() {
+ return VERSION;
+ }
+
+ @Override
+ protected String getVersionFileName() {
+ return "history.version"; //$NON-NLS-1$
+ }
+
+ @Override
+ protected Object readEntryValue(DataInputStream source) throws IOException {
+ int length = source.readUnsignedShort();
+ byte[][] uuids = new byte[length][HistoryEntry.DATA_LENGTH];
+ for (int j = 0; j < uuids.length; j++)
+ source.read(uuids[j]);
+ return uuids;
+ }
+
+ @Override
+ protected void writeEntryValue(DataOutputStream destination, Object entryValue) throws IOException {
+ byte[][] uuids = (byte[][]) entryValue;
+ destination.writeShort(uuids.length);
+ for (int j = 0; j < uuids.length; j++)
+ destination.write(uuids[j]);
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/HistoryStore2.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/HistoryStore2.java
new file mode 100644
index 0000000000..6a348bf521
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/HistoryStore2.java
@@ -0,0 +1,389 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2015 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.localstore;
+
+import java.io.InputStream;
+import java.util.*;
+import org.eclipse.core.filesystem.*;
+import org.eclipse.core.internal.localstore.Bucket.Entry;
+import org.eclipse.core.internal.localstore.HistoryBucket.HistoryEntry;
+import org.eclipse.core.internal.resources.*;
+import org.eclipse.core.internal.utils.*;
+import org.eclipse.core.resources.*;
+import org.eclipse.core.runtime.*;
+
+public class HistoryStore2 implements IHistoryStore {
+
+ class HistoryCopyVisitor extends Bucket.Visitor {
+ private List changes = new ArrayList();
+ private IPath destination;
+ private IPath source;
+
+ public HistoryCopyVisitor(IPath source, IPath destination) {
+ this.source = source;
+ this.destination = destination;
+ }
+
+ @Override
+ public void afterSaving(Bucket bucket) throws CoreException {
+ saveChanges();
+ changes.clear();
+ }
+
+ private void saveChanges() throws CoreException {
+ if (changes.isEmpty())
+ return;
+ // make effective all changes collected
+ Iterator i = changes.iterator();
+ HistoryEntry entry = i.next();
+ tree.loadBucketFor(entry.getPath());
+ HistoryBucket bucket = (HistoryBucket) tree.getCurrent();
+ bucket.addBlobs(entry);
+ while (i.hasNext())
+ bucket.addBlobs(i.next());
+ bucket.save();
+ }
+
+ @Override
+ public int visit(Entry sourceEntry) {
+ IPath destinationPath = destination.append(sourceEntry.getPath().removeFirstSegments(source.segmentCount()));
+ HistoryEntry destinationEntry = new HistoryEntry(destinationPath, (HistoryEntry) sourceEntry);
+ // we may be copying to the same source bucket, collect to make change effective later
+ // since we cannot make changes to it while iterating
+ changes.add(destinationEntry);
+ return CONTINUE;
+ }
+ }
+
+ private BlobStore blobStore;
+ private Set blobsToRemove = new HashSet();
+ final BucketTree tree;
+ private Workspace workspace;
+
+ public HistoryStore2(Workspace workspace, IFileStore store, int limit) {
+ this.workspace = workspace;
+ try {
+ store.mkdir(EFS.NONE, null);
+ } catch (CoreException e) {
+ //ignore the failure here because there is no way to surface it.
+ //any attempt to write to the store will throw an appropriate exception
+ }
+ this.blobStore = new BlobStore(store, limit);
+ this.tree = new BucketTree(workspace, new HistoryBucket());
+ }
+
+ /**
+ * @see IHistoryStore#addState(IPath, IFileStore, IFileInfo, boolean)
+ */
+ @Override
+ public synchronized IFileState addState(IPath key, IFileStore localFile, IFileInfo info, boolean moveContents) {
+ long lastModified = info.getLastModified();
+ if (Policy.DEBUG_HISTORY)
+ Policy.debug("History: Adding state for key: " + key + ", file: " + localFile + ", timestamp: " + lastModified + ", size: " + localFile.fetchInfo().getLength()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
+ if (!isValid(localFile, info))
+ return null;
+ UniversalUniqueIdentifier uuid = null;
+ try {
+ uuid = blobStore.addBlob(localFile, moveContents);
+ tree.loadBucketFor(key);
+ HistoryBucket currentBucket = (HistoryBucket) tree.getCurrent();
+ currentBucket.addBlob(key, uuid, lastModified);
+ // currentBucket.save();
+ } catch (CoreException e) {
+ log(e);
+ }
+ return new FileState(this, key, lastModified, uuid);
+ }
+
+ @Override
+ public synchronized Set allFiles(IPath root, int depth, IProgressMonitor monitor) {
+ final Set allFiles = new HashSet();
+ try {
+ tree.accept(new Bucket.Visitor() {
+ @Override
+ public int visit(Entry fileEntry) {
+ allFiles.add(fileEntry.getPath());
+ return CONTINUE;
+ }
+ }, root, depth == IResource.DEPTH_INFINITE ? BucketTree.DEPTH_INFINITE : depth);
+ } catch (CoreException e) {
+ log(e);
+ }
+ return allFiles;
+ }
+
+ /**
+ * Applies the clean-up policy to an entry.
+ */
+ protected void applyPolicy(HistoryEntry fileEntry, int maxStates, long minTimeStamp) {
+ for (int i = 0; i < fileEntry.getOccurrences(); i++) {
+ if (i < maxStates && fileEntry.getTimestamp(i) >= minTimeStamp)
+ continue;
+ // "delete" the current uuid
+ blobsToRemove.add(fileEntry.getUUID(i));
+ fileEntry.deleteOccurrence(i);
+ }
+ }
+
+ /**
+ * Applies the clean-up policy to a subtree.
+ */
+ private void applyPolicy(IPath root) throws CoreException {
+ IWorkspaceDescription description = workspace.internalGetDescription();
+ final long minimumTimestamp = System.currentTimeMillis() - description.getFileStateLongevity();
+ final int maxStates = description.getMaxFileStates();
+ // apply policy to the given tree
+ tree.accept(new Bucket.Visitor() {
+ @Override
+ public int visit(Entry entry) {
+ applyPolicy((HistoryEntry) entry, maxStates, minimumTimestamp);
+ return CONTINUE;
+ }
+ }, root, BucketTree.DEPTH_INFINITE);
+ tree.getCurrent().save();
+ }
+
+ @Override
+ public synchronized void clean(final IProgressMonitor monitor) {
+ long start = System.currentTimeMillis();
+ try {
+ monitor.beginTask(Messages.resources_pruningHistory, IProgressMonitor.UNKNOWN);
+ IWorkspaceDescription description = workspace.internalGetDescription();
+ final long minimumTimestamp = System.currentTimeMillis() - description.getFileStateLongevity();
+ final int maxStates = description.getMaxFileStates();
+ final int[] entryCount = new int[1];
+ if (description.isApplyFileStatePolicy()) {
+ tree.accept(new Bucket.Visitor() {
+ @Override
+ public int visit(Entry fileEntry) {
+ if (monitor.isCanceled())
+ return STOP;
+ entryCount[0] += fileEntry.getOccurrences();
+ applyPolicy((HistoryEntry) fileEntry, maxStates, minimumTimestamp);
+ // remove unreferenced blobs, when blobsToRemove size is greater than 100
+ removeUnreferencedBlobs(100);
+ return monitor.isCanceled() ? STOP : CONTINUE;
+ }
+ }, Path.ROOT, BucketTree.DEPTH_INFINITE);
+ }
+ if (Policy.DEBUG_HISTORY) {
+ Policy.debug("Time to apply history store policies: " + (System.currentTimeMillis() - start) + "ms."); //$NON-NLS-1$ //$NON-NLS-2$
+ Policy.debug("Total number of history store entries: " + entryCount[0]); //$NON-NLS-1$
+ }
+ // remove all remaining unreferenced blobs
+ removeUnreferencedBlobs(0);
+ } catch (Exception e) {
+ String message = Messages.history_problemsCleaning;
+ ResourceStatus status = new ResourceStatus(IResourceStatus.FAILED_DELETE_LOCAL, null, message, e);
+ Policy.log(status);
+ } finally {
+ monitor.done();
+ }
+ }
+
+ /*
+ * Remove blobs from the blobStore. When the size of blobsToRemove exceeds the limit,
+ * remove the given blobs from blobStore. If the limit is zero or negative, remove blobs
+ * regardless of the limit.
+ */
+ void removeUnreferencedBlobs(int limit) {
+ if (limit <= 0 || limit <= blobsToRemove.size()) {
+ long start = System.currentTimeMillis();
+ // remove unreferenced blobs
+ blobStore.deleteBlobs(blobsToRemove);
+ if (Policy.DEBUG_HISTORY)
+ Policy.debug("Time to remove " + blobsToRemove.size() + " unreferenced blobs: " + (System.currentTimeMillis() - start) + "ms."); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$
+ blobsToRemove = new HashSet();
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.internal.localstore.IHistoryStore#closeHistory(org.eclipse.core.resources.IResource)
+ */
+ @Override
+ public void closeHistoryStore(IResource resource) {
+ try {
+ tree.getCurrent().save();
+ tree.getCurrent().flush();
+ } catch (CoreException e) {
+ log(e);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.core.internal.localstore.IHistoryStore#copyHistory(org.eclipse.core.resources.IResource, org.eclipse.core.resources.IResource, boolean)
+ */
+ @Override
+ public synchronized void copyHistory(IResource sourceResource, IResource destinationResource, boolean moving) {
+ // return early if either of the paths are null or if the source and
+ // destination are the same.
+ if (sourceResource == null || destinationResource == null) {
+ String message = Messages.history_copyToNull;
+ ResourceStatus status = new ResourceStatus(IResourceStatus.INTERNAL_ERROR, null, message, null);
+ Policy.log(status);
+ return;
+ }
+ if (sourceResource.equals(destinationResource)) {
+ String message = Messages.history_copyToSelf;
+ ResourceStatus status = new ResourceStatus(IResourceStatus.INTERNAL_ERROR, sourceResource.getFullPath(), message, null);
+ Policy.log(status);
+ return;
+ }
+
+ final IPath source = sourceResource.getFullPath();
+ final IPath destination = destinationResource.getFullPath();
+ Assert.isLegal(source.segmentCount() > 0);
+ Assert.isLegal(destination.segmentCount() > 0);
+ Assert.isLegal(source.segmentCount() > 1 || destination.segmentCount() == 1);
+
+ try {
+ // special case: we are moving a project
+ if (moving && sourceResource.getType() == IResource.PROJECT) {
+ // flush the tree to avoid confusion if another project is created with the same name
+ final Bucket bucket = tree.getCurrent();
+ bucket.save();
+ bucket.flush();
+ return;
+ }
+ // copy history by visiting the source tree
+ HistoryCopyVisitor copyVisitor = new HistoryCopyVisitor(source, destination);
+ tree.accept(copyVisitor, source, BucketTree.DEPTH_INFINITE);
+ // apply clean-up policy to the destination tree
+ applyPolicy(destinationResource.getFullPath());
+ } catch (CoreException e) {
+ log(e);
+ }
+ }
+
+ @Override
+ public boolean exists(IFileState target) {
+ return blobStore.fileFor(((FileState) target).getUUID()).fetchInfo().exists();
+ }
+
+ @Override
+ public InputStream getContents(IFileState target) throws CoreException {
+ if (!target.exists()) {
+ String message = Messages.history_notValid;
+ throw new ResourceException(IResourceStatus.FAILED_READ_LOCAL, target.getFullPath(), message, null);
+ }
+ return blobStore.getBlob(((FileState) target).getUUID());
+ }
+
+ @Override
+ public synchronized IFileState[] getStates(IPath filePath, IProgressMonitor monitor) {
+ try {
+ tree.loadBucketFor(filePath);
+ HistoryBucket currentBucket = (HistoryBucket) tree.getCurrent();
+ HistoryEntry fileEntry = currentBucket.getEntry(filePath);
+ if (fileEntry == null || fileEntry.isEmpty())
+ return new IFileState[0];
+ IFileState[] states = new IFileState[fileEntry.getOccurrences()];
+ for (int i = 0; i < states.length; i++)
+ states[i] = new FileState(this, fileEntry.getPath(), fileEntry.getTimestamp(i), fileEntry.getUUID(i));
+ return states;
+ } catch (CoreException ce) {
+ log(ce);
+ return new IFileState[0];
+ }
+ }
+
+ public BucketTree getTree() {
+ return tree;
+ }
+
+ /**
+ * Return a boolean value indicating whether or not the given file
+ * should be added to the history store based on the current history
+ * store policies.
+ *
+ * @param localFile the file to check
+ * @return true if this file should be added to the history
+ * store and false otherwise
+ */
+ private boolean isValid(IFileStore localFile, IFileInfo info) {
+ WorkspaceDescription description = workspace.internalGetDescription();
+ if (!description.isApplyFileStatePolicy())
+ return true;
+ long length = info.getLength();
+ boolean result = length <= description.getMaxFileStateSize();
+ if (Policy.DEBUG_HISTORY && !result)
+ Policy.debug("History: Ignoring file (too large). File: " + localFile.toString() + //$NON-NLS-1$
+ ", size: " + length + //$NON-NLS-1$
+ ", max: " + description.getMaxFileStateSize()); //$NON-NLS-1$
+ return result;
+ }
+
+ /**
+ * Logs a CoreException
+ */
+ private void log(CoreException e) {
+ //create a new status to wrap the exception if there is no exception in the status
+ IStatus status = e.getStatus();
+ if (status.getException() == null)
+ status = new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, IResourceStatus.FAILED_WRITE_METADATA, "Internal error in history store", e); //$NON-NLS-1$
+ Policy.log(status);
+ }
+
+ @Override
+ public synchronized void remove(IPath root, IProgressMonitor monitor) {
+ try {
+ final Set tmpBlobsToRemove = blobsToRemove;
+ tree.accept(new Bucket.Visitor() {
+ @Override
+ public int visit(Entry fileEntry) {
+ for (int i = 0; i < fileEntry.getOccurrences(); i++)
+ // remember we need to delete the files later
+ tmpBlobsToRemove.add(((HistoryEntry) fileEntry).getUUID(i));
+ fileEntry.delete();
+ return CONTINUE;
+ }
+ }, root, BucketTree.DEPTH_INFINITE);
+ } catch (CoreException ce) {
+ log(ce);
+ }
+ }
+
+ /**
+ * @see IHistoryStore#removeGarbage()
+ */
+ @Override
+ public synchronized void removeGarbage() {
+ try {
+ final Set tmpBlobsToRemove = blobsToRemove;
+ tree.accept(new Bucket.Visitor() {
+ @Override
+ public int visit(Entry fileEntry) {
+ for (int i = 0; i < fileEntry.getOccurrences(); i++)
+ // remember we need to delete the files later
+ tmpBlobsToRemove.remove(((HistoryEntry) fileEntry).getUUID(i));
+ return CONTINUE;
+ }
+ }, Path.ROOT, BucketTree.DEPTH_INFINITE);
+ blobStore.deleteBlobs(blobsToRemove);
+ blobsToRemove = new HashSet();
+ } catch (Exception e) {
+ String message = Messages.history_problemsCleaning;
+ ResourceStatus status = new ResourceStatus(IResourceStatus.FAILED_DELETE_LOCAL, null, message, e);
+ Policy.log(status);
+ }
+ }
+
+ @Override
+ public synchronized void shutdown(IProgressMonitor monitor) throws CoreException {
+ tree.close();
+ }
+
+ @Override
+ public void startup(IProgressMonitor monitor) {
+ // nothing to be done
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/IHistoryStore.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/IHistoryStore.java
new file mode 100644
index 0000000000..d677525034
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/IHistoryStore.java
@@ -0,0 +1,178 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.localstore;
+
+import java.io.InputStream;
+import java.util.Set;
+import org.eclipse.core.filesystem.IFileInfo;
+import org.eclipse.core.filesystem.IFileStore;
+import org.eclipse.core.internal.resources.IManager;
+import org.eclipse.core.resources.IFileState;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.*;
+
+/**
+ * The history store is an association of paths to file states.
+ * Typically the path is the full path of a resource in the workspace.
+ *
+ * History store policies are stored in the org.eclipse.core.resources'
+ * plug-in preferences.
+ *
+ *
+ * @since 3.1
+ */
+public interface IHistoryStore extends IManager {
+
+ /**
+ * Add an entry to the history store, represented by the given key. Return the
+ * file state for the newly created entry ornull if it couldn't
+ * be created.
+ *
+ * Note: Depending on the history store implementation, some of the history
+ * store policies can be applied during this method call to determine whether
+ * or not the entry should be added to the store.
+ *
+ * @param key full workspace path to resource being logged
+ * @param localFile local file system file handle
+ * @param fileInfo The IFileInfo for the entry
+ * @return the file state or null
+ *
+ * TODO: should this method take a progress monitor?
+ *
+ * TODO: look at #getFileFor(). Is there a case where we wouldn't want to
+ * copy over the file attributes to the local history? If we did that here then
+ * we wouldn't have to have that other API.
+ */
+ public IFileState addState(IPath key, IFileStore localFile, IFileInfo fileInfo, boolean moveContents);
+
+ /**
+ * Returns the paths of all files with entries in this history store at or below
+ * the given workspace resource path to the given depth. Returns an
+ * empty set if there are none.
+ *
+ * This method is long-running; progress and cancellation are provided
+ * by the given progress monitor.
+ *
+ * @param path full workspace path to resource
+ * @param depth depth limit: one of DEPTH_ZERO, DEPTH_ONE
+ * or DEPTH_INFINITE
+ * @param monitor a progress monitor, or null if progress
+ * reporting is not desired
+ * @return the set of paths for files that have at least one history entry
+ * (element type: IPath)
+ */
+ public Set allFiles(IPath path, int depth, IProgressMonitor monitor);
+
+ /**
+ * Clean this store applying the current policies.
+ *
+ * Note: The history store policies are stored as part of
+ * the org.eclipse.core.resource plug-in's preferences and
+ * include such settings as: maximum file size, maximum number
+ * of states per file, file expiration date, etc.
+ *
+ *
+ * Note: Depending on the implementation of the history store,
+ * if all the history store policies are applying when the entries
+ * are first added to the store then this method might be a no-op.
+ *
+ *
+ * This method is long-running; progress and cancellation are provided
+ * by the given progress monitor.
+ *
+ * @param monitor a progress monitor, or null if progress
+ * reporting is not desired
+ */
+ public void clean(IProgressMonitor monitor);
+
+ /**
+ * Closes the history store for the given resource.
+ */
+ public void closeHistoryStore(IResource resource);
+
+ /**
+ * Copies the history store information from the source path given destination path.
+ * Note that destination may already have some history store information. Also note
+ * that this is a DEPTH_INFINITY operation. That is, history will be copied for partial
+ * matches of the source path.
+ *
+ * @param source the resource containing the original copy of the history store information
+ * @param destination the target resource where to copy the history
+ * @param moving whether the history is being copied due to a resource move
+ *
+ * TODO: should this method take a progress monitor?
+ */
+ public void copyHistory(IResource source, IResource destination, boolean moving);
+
+ /**
+ * Verifies existence of specified resource in the history store. Returns
+ * true if the file state exists and false
+ * otherwise.
+ *
+ * Note: This method cannot take a progress monitor since it is surfaced
+ * to the real API via IFileState#exists() which doesn't take a progress
+ * monitor.
+ *
+ * @param target the file state to be verified
+ * @return true if file state exists,
+ * and false otherwise
+ */
+ public boolean exists(IFileState target);
+
+ /**
+ * Returns an input stream containing the file contents of the specified state.
+ * The user is responsible for closing the returned stream.
+ *
+ * Note: This method cannot take a progress monitor since it is
+ * surfaced through to the real API via IFileState#getContents which
+ * doesn't take one.
+ *
+ * @param target File state for which an input stream is requested
+ * @return the stream for requested file state
+ */
+ public InputStream getContents(IFileState target) throws CoreException;
+
+ /**
+ * Returns an array of all states available for the specified resource path or
+ * an empty array if none.
+ *
+ * This method is long-running; progress and cancellation are provided
+ * by the given progress monitor.
+ *
+ * @param path the resource path
+ * @param monitor a progress monitor, or null if progress
+ * reporting is not desired
+ * @return the list of file states
+ */
+ public IFileState[] getStates(IPath path, IProgressMonitor monitor);
+
+ /**
+ * Remove all of the file states for the given resource path and
+ * all its children. If the workspace root path is the given argument,
+ * then all history for this store is removed.
+ *
+ * This method is long-running; progress and cancellation are provided
+ * by the given progress monitor.
+ *
+ * @param path the resource path whose history is to be removed
+ * @param monitor a progress monitor, or null if progress
+ * reporting is not desired
+ */
+ public void remove(IPath path, IProgressMonitor monitor);
+
+ /**
+ * Go through the history store and remove all of the unreferenced states.
+ *
+ * As of 3.0, this method is used for testing purposes only. Otherwise the history
+ * store is garbage collected during the #clean method.
+ */
+ public void removeGarbage();
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/ILocalStoreConstants.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/ILocalStoreConstants.java
new file mode 100644
index 0000000000..41fa33c582
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/ILocalStoreConstants.java
@@ -0,0 +1,31 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2005 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.localstore;
+
+public interface ILocalStoreConstants {
+
+ /** Common constants for History Store classes. */
+ public final static int SIZE_LASTMODIFIED = 8;
+ public static final int SIZE_COUNTER = 1;
+ public static final int SIZE_KEY_SUFFIX = SIZE_LASTMODIFIED + SIZE_COUNTER;
+
+ /** constants for safe chunky streams */
+
+ // 40b18b8123bc00141a2596e7a393be1e
+ public static final byte[] BEGIN_CHUNK = {64, -79, -117, -127, 35, -68, 0, 20, 26, 37, -106, -25, -93, -109, -66, 30};
+
+ // c058fbf323bc00141a51f38c7bbb77c6
+ public static final byte[] END_CHUNK = {-64, 88, -5, -13, 35, -68, 0, 20, 26, 81, -13, -116, 123, -69, 119, -58};
+
+ /** chunk delimiter size */
+ // BEGIN_CHUNK and END_CHUNK must have the same length
+ public static final int CHUNK_DELIMITER_SIZE = BEGIN_CHUNK.length;
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/IUnifiedTreeVisitor.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/IUnifiedTreeVisitor.java
new file mode 100644
index 0000000000..c0c328a2ae
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/IUnifiedTreeVisitor.java
@@ -0,0 +1,20 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2005 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.localstore;
+
+import org.eclipse.core.runtime.CoreException;
+
+public interface IUnifiedTreeVisitor {
+ /**
+ * Returns true to visit the members of this node and false otherwise.
+ */
+ public boolean visit(UnifiedTreeNode node) throws CoreException;
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/IsSynchronizedVisitor.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/IsSynchronizedVisitor.java
new file mode 100644
index 0000000000..57590a11b6
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/IsSynchronizedVisitor.java
@@ -0,0 +1,59 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * James Blackburn (Broadcom Corp.) - ongoing development
+ *******************************************************************************/
+package org.eclipse.core.internal.localstore;
+
+import org.eclipse.core.internal.resources.Resource;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.IProgressMonitor;
+
+/**
+ * Visits a unified tree, and throws a ResourceChangedException on the first
+ * node that is discovered to be out of sync. The exception that is thrown
+ * will not have any meaningful status, message, or stack trace. However it
+ * does contain the target resource which can be used to bring the Resource
+ * back into sync.
+ */
+public class IsSynchronizedVisitor extends CollectSyncStatusVisitor {
+ static class ResourceChangedException extends RuntimeException {
+ private static final long serialVersionUID = 1L;
+ public final IResource target;
+ public ResourceChangedException(IResource target) {
+ this.target = target;
+ }
+ }
+
+ /**
+ * Creates a new IsSynchronizedVisitor.
+ */
+ public IsSynchronizedVisitor(IProgressMonitor monitor) {
+ super("", monitor); //$NON-NLS-1$
+ }
+
+ /**
+ * @see CollectSyncStatusVisitor#changed(Resource)
+ */
+ @Override
+ protected void changed(Resource target) {
+ throw new ResourceChangedException(target);
+ }
+
+ @Override
+ protected void fileToFolder(UnifiedTreeNode node, Resource target) {
+ changed((Resource)workspace.getRoot().getFolder(target.getFullPath()));
+ }
+
+ @Override
+ protected void folderToFile(UnifiedTreeNode node, Resource target) {
+ // Pass correct gender to changed for notification and async-refresh
+ changed((Resource)workspace.getRoot().getFile(target.getFullPath()));
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/PrefixPool.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/PrefixPool.java
new file mode 100644
index 0000000000..a382386c8d
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/PrefixPool.java
@@ -0,0 +1,207 @@
+/*******************************************************************************
+ * Copyright (c) 2007 Wind River Systems, Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Martin Oberhuber (Wind River) - initial API and implementation for [105554]
+ *******************************************************************************/
+
+package org.eclipse.core.internal.localstore;
+
+import java.util.Arrays;
+
+/**
+ * A pool of Strings for doing prefix checks against multiple
+ * candidates.
+ *
+ * Allows to enter a list of Strings, and then perform the
+ * following checks:
+ *
+ *
{@link #containsAsPrefix(String)} - check whether a given
+ * String s is a prefix of any String in the pool.
+ *
{@link #hasPrefixOf(String)} - check whether any String
+ * in the pool is a prefix of the given String s.
+ *
+ * The prefix pool is always kept normalized, i.e. no element of
+ * the pool is a prefix of any other element in the pool. In order
+ * to maintain this constraint, there are two methods for adding
+ * Strings to the pool:
+ *
+ *
{@link #insertLonger(String)} - add a String s to the pool,
+ * and remove any existing prefix of s from the pool.
+ *
{@link #insertShorter(String)} - add a String s to the pool,
+ * and remove any existing Strings sx from the pool which
+ * contain s as prefix.
+ *
+ * The PrefixPool grows as needed when adding Strings. Typically,
+ * it is used for prefix checks on absolute paths of a tree.
+ *
+ * This class is not thread-safe: no two threads may add or
+ * check items at the same time.
+ *
+ * @since 3.3
+ */
+public class PrefixPool {
+ private String[] pool;
+ private int size;
+
+ /**
+ * Constructor.
+ * @param initialCapacity the initial size of the
+ * internal array holding the String pool. Must
+ * be greater than 0.
+ */
+ public PrefixPool(int initialCapacity) {
+ if (initialCapacity <= 0)
+ throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity); //$NON-NLS-1$
+ pool = new String[initialCapacity];
+ size = 0;
+ }
+
+ /**
+ * Clears the prefix pool, allowing all items to be
+ * garbage collected. Does not change the capacity
+ * of the pool.
+ */
+ public/*synchronized*/void clear() {
+ Arrays.fill(pool, 0, size, null);
+ size = 0;
+ }
+
+ /**
+ * Return the current size of prefix pool.
+ * @return the number of elements in the pool.
+ */
+ public/*synchronized*/int size() {
+ return size;
+ }
+
+ /**
+ * Ensure that there is room for at least one more element.
+ */
+ private void checkCapacity() {
+ if (size + 1 >= pool.length) {
+ String[] newprefixList = new String[2 * pool.length];
+ System.arraycopy(pool, 0, newprefixList, 0, pool.length);
+ Arrays.fill(pool, null); //help the garbage collector
+ pool = newprefixList;
+ }
+ }
+
+ /**
+ * Insert a String s into the pool of known prefixes, removing
+ * any existing prefix of it.
+ *
+ * If any existing prefix of this String is found in the pool,
+ * it is replaced by the new longer one in order to maintain
+ * the constraint of keeping the pool normalized.
+ *
+ * If it turns out that s is actually a prefix or equal to
+ * an existing element in the pool (so it is essentially
+ * shorter), this method returns with no operation in order
+ * to maintain the constraint that the pool remains normalized.
+ *
+ * @param s the String to insert.
+ */
+ public/*synchronized*/void insertLonger(String s) {
+ //check in reverse order since we expect some locality
+ for (int i = size - 1; i >= 0; i--) {
+ if (pool[i].startsWith(s)) {
+ //prefix of an existing String --> no-op
+ return;
+ } else if (s.startsWith(pool[i])) {
+ //replace, since a longer s has more prefixes than a short one
+ pool[i] = s;
+ return;
+ }
+ }
+ checkCapacity();
+ pool[size] = s;
+ size++;
+ }
+
+ /**
+ * Insert a String s into the pool of known prefixes, removing
+ * any Strings that have s as prefix.
+ *
+ * If this String is a prefix of any existing String in the pool,
+ * all elements that contain the new String as prefix are removed
+ * and return value true is returned.
+ *
+ * Otherwise, the new String is added to the pool unless an
+ * equal String or e prefix of it exists there already (so
+ * it is essentially equal or longer than an existing prefix).
+ * In all these cases, false is returned since
+ * no prefixes were replaced.
+ *
+ * @param s the String to insert.
+ * @return trueif any longer elements have been
+ * removed.
+ */
+ public/*synchronized*/boolean insertShorter(String s) {
+ boolean replaced = false;
+ //check in reverse order since we expect some locality
+ for (int i = size - 1; i >= 0; i--) {
+ if (s.startsWith(pool[i])) {
+ //longer or equal to an existing prefix - nothing to do
+ return false;
+ } else if (pool[i].startsWith(s)) {
+ if (replaced) {
+ //replaced before, so shrink the array.
+ //Safe since we are iterating in reverse order.
+ System.arraycopy(pool, i + 1, pool, i, size - i - 1);
+ size--;
+ pool[size] = null;
+ } else {
+ //replace, since this is a shorter s
+ pool[i] = s;
+ replaced = true;
+ }
+ }
+ }
+ if (!replaced) {
+ //append at the end
+ checkCapacity();
+ pool[size] = s;
+ size++;
+ }
+ return replaced;
+ }
+
+ /**
+ * Check if the given String s is a prefix of any of Strings
+ * in the pool.
+ * @param s a s to check for being a prefix
+ * @return true if the passed s is a prefix
+ * of any of the Strings contained in the pool.
+ */
+ public/*synchronized*/boolean containsAsPrefix(String s) {
+ //check in reverse order since we expect some locality
+ for (int i = size - 1; i >= 0; i--) {
+ if (pool[i].startsWith(s)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Test if the String pool contains any one that is a prefix
+ * of the given String s.
+ * @param s the String to test
+ * @return true if the String pool contains a
+ * prefix of the given String.
+ */
+ public/*synchronized*/boolean hasPrefixOf(String s) {
+ for (int i = size - 1; i >= 0; i--) {
+ if (s.startsWith(pool[i])) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/RefreshLocalAliasVisitor.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/RefreshLocalAliasVisitor.java
new file mode 100644
index 0000000000..45d5f1b69a
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/RefreshLocalAliasVisitor.java
@@ -0,0 +1,110 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2014 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.localstore;
+
+import org.eclipse.core.filesystem.IFileStore;
+import org.eclipse.core.internal.resources.Container;
+import org.eclipse.core.internal.resources.Resource;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+
+/**
+ * Performs a local refresh, and additionally updates all aliases of the
+ * refreshed resource.
+ */
+public class RefreshLocalAliasVisitor extends RefreshLocalVisitor {
+ public RefreshLocalAliasVisitor(IProgressMonitor monitor) {
+ super(monitor);
+ }
+
+ @Override
+ protected void createResource(UnifiedTreeNode node, Resource target) throws CoreException {
+ super.createResource(node, target);
+ IFileStore store = node.getStore();
+ if (store == null)
+ return;
+ IResource[] aliases = workspace.getAliasManager().computeAliases(target, store);
+ if (aliases != null)
+ for (int i = 0; i < aliases.length; i++) {
+ if (aliases[i].getProject().isOpen() && !((Resource) aliases[i]).isFiltered())
+ super.createResource(node, (Resource) aliases[i]);
+ }
+ }
+
+ @Override
+ protected void deleteResource(UnifiedTreeNode node, Resource target) throws CoreException {
+ super.deleteResource(node, target);
+ IFileStore store = node.getStore();
+ if (store == null)
+ return;
+ IResource[] aliases = workspace.getAliasManager().computeAliases(target, store);
+ if (aliases != null) {
+ boolean wasFilteredOut = false;
+ if (store.fetchInfo() != null && store.fetchInfo().exists())
+ wasFilteredOut = target.isFiltered();
+ for (int i = 0; i < aliases.length; i++) {
+ if (aliases[i].getProject().isOpen()) {
+ if (wasFilteredOut) {
+ if (((Resource) aliases[i]).isFiltered())
+ super.deleteResource(node, (Resource) aliases[i]);
+ }
+ else
+ super.deleteResource(node, (Resource) aliases[i]);
+ }
+ }
+ }
+ }
+
+ @Override
+ protected void resourceChanged(UnifiedTreeNode node, Resource target) {
+ super.resourceChanged(node, target);
+ IFileStore store = node.getStore();
+ if (store == null)
+ return;
+ IResource[] aliases = workspace.getAliasManager().computeAliases(target, store);
+ if (aliases != null)
+ for (int i = 0; i < aliases.length; i++) {
+ if (aliases[i].getProject().isOpen())
+ super.resourceChanged(node, (Resource) aliases[i]);
+ }
+ }
+
+ @Override
+ protected void fileToFolder(UnifiedTreeNode node, Resource target) throws CoreException {
+ super.fileToFolder(node, target);
+ IFileStore store = node.getStore();
+ if (store == null)
+ return;
+ IResource[] aliases = workspace.getAliasManager().computeAliases(target, store);
+ if (aliases != null)
+ for (int i = 0; i < aliases.length; i++)
+ super.fileToFolder(node, (Resource) aliases[i]);
+ }
+
+ @Override
+ protected void folderToFile(UnifiedTreeNode node, Resource target) throws CoreException {
+ super.folderToFile(node, target);
+ IFileStore store = node.getStore();
+ if (store == null)
+ return;
+ IResource[] aliases = workspace.getAliasManager().computeAliases(target, store);
+ if (aliases != null)
+ for (int i = 0; i < aliases.length; i++)
+ super.folderToFile(node, (Resource) aliases[i]);
+ }
+
+ @Override
+ protected void refresh(Container parent) throws CoreException {
+ parent.getLocalManager().refresh(parent, IResource.DEPTH_ZERO, true, null);
+ }
+
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/RefreshLocalVisitor.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/RefreshLocalVisitor.java
new file mode 100644
index 0000000000..668ae01605
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/RefreshLocalVisitor.java
@@ -0,0 +1,334 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2015 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.localstore;
+
+import org.eclipse.core.internal.resources.*;
+import org.eclipse.core.internal.utils.Messages;
+import org.eclipse.core.internal.utils.Policy;
+import org.eclipse.core.resources.*;
+import org.eclipse.core.runtime.*;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * Visits a unified tree, and synchronizes the file system with the
+ * resource tree. After the visit is complete, the file system will
+ * be synchronized with the workspace tree with respect to
+ * resource existence, gender, and timestamp.
+ */
+public class RefreshLocalVisitor implements IUnifiedTreeVisitor, ILocalStoreConstants {
+ /** control constants */
+ protected static final int RL_UNKNOWN = 0;
+ protected static final int RL_IN_SYNC = 1;
+ protected static final int RL_NOT_IN_SYNC = 2;
+
+ /*
+ * Fields for progress monitoring algorithm.
+ * Initially, give progress for every 4 resources, double
+ * this value at halfway point, then reset halfway point
+ * to be half of remaining work. (this gives an infinite
+ * series that converges at total work after an infinite
+ * number of resources).
+ */
+ public static final int TOTAL_WORK = 250;
+ private int currentIncrement = 4;
+ private int halfWay = TOTAL_WORK / 2;
+ private int nextProgress = currentIncrement;
+ private int worked = 0;
+
+ protected MultiStatus errors;
+ protected IProgressMonitor monitor;
+ protected boolean resourceChanged;
+ protected Workspace workspace;
+
+ public RefreshLocalVisitor(IProgressMonitor monitor) {
+ this.monitor = monitor;
+ workspace = (Workspace) ResourcesPlugin.getWorkspace();
+ resourceChanged = false;
+ String msg = Messages.resources_errorMultiRefresh;
+ errors = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.FAILED_READ_LOCAL, msg, null);
+ }
+
+ /**
+ * This method has the same implementation as resourceChanged but as they are different
+ * cases, we prefer to use different methods.
+ */
+ protected void contentAdded(UnifiedTreeNode node, Resource target) {
+ resourceChanged(node, target);
+ }
+
+ protected void createResource(UnifiedTreeNode node, Resource target) throws CoreException {
+ ResourceInfo info = target.getResourceInfo(false, false);
+ int flags = target.getFlags(info);
+ if (target.exists(flags, false))
+ return;
+ /* make sure target's parent exists */
+ IContainer parent = target.getParent();
+ if (parent.getType() == IResource.FOLDER)
+ ((Folder) target.getParent()).ensureExists(monitor);
+ /* Use the basic file creation protocol since we don't want to create any content on disk. */
+ info = workspace.createResource(target, false);
+ /* Mark this resource as having unknown children */
+ info.set(ICoreConstants.M_CHILDREN_UNKNOWN);
+ target.getLocalManager().updateLocalSync(info, node.getLastModified());
+ }
+
+ protected void deleteResource(UnifiedTreeNode node, Resource target) throws CoreException {
+ ResourceInfo info = target.getResourceInfo(false, false);
+ int flags = target.getFlags(info);
+ //don't delete linked resources
+ if (ResourceInfo.isSet(flags, ICoreConstants.M_LINK)) {
+ //just clear local sync info
+ info = target.getResourceInfo(false, true);
+ //handle concurrent deletion
+ if (info != null) {
+ info.clearModificationStamp();
+ target.getLocalManager().updateLocalSync(info, node.getLastModified());
+ }
+ return;
+ }
+ if (target.exists(flags, false))
+ target.deleteResource(true, errors);
+ node.setExistsWorkspace(false);
+ }
+
+ protected void fileToFolder(UnifiedTreeNode node, Resource target) throws CoreException {
+ ResourceInfo info = target.getResourceInfo(false, false);
+ int flags = target.getFlags(info);
+ if (target.exists(flags, true)) {
+ target = (Folder) ((File) target).changeToFolder();
+ } else {
+ if (!target.exists(flags, false)) {
+ target = (Resource) workspace.getRoot().getFolder(target.getFullPath());
+ // Use the basic file creation protocol since we don't want to create any content on disk.
+ workspace.createResource(target, false);
+ }
+ }
+ node.setResource(target);
+ info = target.getResourceInfo(false, true);
+ target.getLocalManager().updateLocalSync(info, node.getLastModified());
+ }
+
+ protected void folderToFile(UnifiedTreeNode node, Resource target) throws CoreException {
+ ResourceInfo info = target.getResourceInfo(false, false);
+ int flags = target.getFlags(info);
+ if (target.exists(flags, true))
+ target = (File) ((Folder) target).changeToFile();
+ else {
+ if (!target.exists(flags, false)) {
+ target = (Resource) workspace.getRoot().getFile(target.getFullPath());
+ // Use the basic file creation protocol since we don't want to
+ // create any content on disk.
+ workspace.createResource(target, false);
+ }
+ }
+ node.setResource(target);
+ info = target.getResourceInfo(false, true);
+ target.getLocalManager().updateLocalSync(info, node.getLastModified());
+ }
+
+ /**
+ * Returns the status of the nodes visited so far. This will be a multi-status
+ * that describes all problems that have occurred, or an OK status if everything
+ * went smoothly.
+ */
+ public IStatus getErrorStatus() {
+ return errors;
+ }
+
+ protected void makeLocal(UnifiedTreeNode node, Resource target) {
+ ResourceInfo info = target.getResourceInfo(false, true);
+ if (info != null)
+ target.getLocalManager().updateLocalSync(info, node.getLastModified());
+ }
+
+ /**
+ * Refreshes the parent of a resource currently being synchronized.
+ */
+ protected void refresh(Container parent) throws CoreException {
+ parent.getLocalManager().refresh(parent, IResource.DEPTH_ZERO, false, null);
+ }
+
+ protected void resourceChanged(UnifiedTreeNode node, Resource target) {
+ ResourceInfo info = target.getResourceInfo(false, true);
+ if (info == null)
+ return;
+ target.getLocalManager().updateLocalSync(info, node.getLastModified());
+ info.incrementContentId();
+ // forget content-related caching flags
+ info.clear(ICoreConstants.M_CONTENT_CACHE);
+ workspace.updateModificationStamp(info);
+ }
+
+ public boolean resourcesChanged() {
+ return resourceChanged;
+ }
+
+ /**
+ * deletion or creation -- Returns:
+ * - RL_IN_SYNC - the resource is in-sync with the file system
+ * - RL_NOT_IN_SYNC - the resource is not in-sync with file system
+ * - RL_UNKNOWN - couldn't determine the sync status for this resource
+ */
+ protected int synchronizeExistence(UnifiedTreeNode node, Resource target) throws CoreException {
+ if (node.existsInWorkspace()) {
+ if (!node.existsInFileSystem()) {
+ // 1. non-local files are always in sync
+ // 2. links to non-existent locations with the modification stamp of IResource.NULL_STAMP are in sync
+ if (target.isLocal(IResource.DEPTH_ZERO) && target.getModificationStamp() != IResource.NULL_STAMP) {
+ deleteResource(node, target);
+ resourceChanged = true;
+ return RL_NOT_IN_SYNC;
+ }
+ return RL_IN_SYNC;
+ }
+ } else {
+ // do we have a gender variant in the workspace?
+ IResource genderVariant = workspace.getRoot().findMember(target.getFullPath());
+ if (genderVariant != null)
+ return RL_UNKNOWN;
+ if (node.existsInFileSystem()) {
+ Container parent = (Container) target.getParent();
+ if (!parent.exists()) {
+ refresh(parent);
+ if (!parent.exists())
+ return RL_NOT_IN_SYNC;
+ }
+ if (!target.getName().equals(node.getLocalName()))
+ return RL_IN_SYNC;
+ if (!Workspace.caseSensitive && node.getLevel() == 0) {
+ // do we have any alphabetic variants in the workspace?
+ IResource variant = target.findExistingResourceVariant(target.getFullPath());
+ if (variant != null) {
+ deleteResource(node, ((Resource) variant));
+ createResource(node, target);
+ resourceChanged = true;
+ return RL_NOT_IN_SYNC;
+ }
+ }
+ createResource(node, target);
+ resourceChanged = true;
+ return RL_NOT_IN_SYNC;
+ }
+ }
+ return RL_UNKNOWN;
+ }
+
+ /**
+ * gender change -- Returns true if gender was in sync.
+ */
+ protected boolean synchronizeGender(UnifiedTreeNode node, Resource target) throws CoreException {
+ if (!node.existsInWorkspace()) {
+ //may be an existing resource in the workspace of different gender
+ IResource genderVariant = workspace.getRoot().findMember(target.getFullPath());
+ if (genderVariant != null)
+ target = (Resource) genderVariant;
+ }
+ if (target.getType() == IResource.FILE) {
+ if (node.isFolder()) {
+ fileToFolder(node, target);
+ resourceChanged = true;
+ return false;
+ }
+ } else {
+ if (!node.isFolder()) {
+ folderToFile(node, target);
+ resourceChanged = true;
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * lastModified
+ */
+ protected void synchronizeLastModified(UnifiedTreeNode node, Resource target) {
+ if (target.isLocal(IResource.DEPTH_ZERO))
+ resourceChanged(node, target);
+ else
+ contentAdded(node, target);
+ resourceChanged = true;
+ }
+
+ @Override
+ public boolean visit(UnifiedTreeNode node) throws CoreException {
+ Policy.checkCanceled(monitor);
+ try {
+ if (node.isErrorInFileSystem())
+ return false; // Don't visit children if we encountered an I/O error
+ Resource target = (Resource) node.getResource();
+ int targetType = target.getType();
+ if (targetType == IResource.PROJECT)
+ return true;
+ if (node.existsInWorkspace() && node.existsInFileSystem()) {
+ /* for folders we only care about updating local status */
+ if (targetType == IResource.FOLDER && node.isFolder()) {
+ // if not local, mark as local
+ if (!target.isLocal(IResource.DEPTH_ZERO))
+ makeLocal(node, target);
+ ResourceInfo info = target.getResourceInfo(false, false);
+ if (info != null && info.getModificationStamp() != IResource.NULL_STAMP)
+ return true;
+ }
+ /* compare file last modified */
+ if (targetType == IResource.FILE && !node.isFolder()) {
+ ResourceInfo info = target.getResourceInfo(false, false);
+ if (info != null && info.getModificationStamp() != IResource.NULL_STAMP && info.getLocalSyncInfo() == node.getLastModified())
+ return true;
+ }
+ } else {
+ if (node.existsInFileSystem() && !Path.EMPTY.isValidSegment(node.getLocalName())) {
+ String message = NLS.bind(Messages.resources_invalidResourceName, node.getLocalName());
+ errors.merge(new ResourceStatus(IResourceStatus.INVALID_RESOURCE_NAME, message));
+ return false;
+ }
+ int state = synchronizeExistence(node, target);
+ if (state == RL_IN_SYNC || state == RL_NOT_IN_SYNC) {
+ if (targetType == IResource.FILE) {
+ try {
+ ((File) target).updateMetadataFiles();
+ } catch (CoreException e) {
+ errors.merge(e.getStatus());
+ }
+ }
+ return true;
+ }
+ }
+ if (node.isSymbolicLink() && !node.existsInFileSystem())
+ return true; // Dangling symbolic links are considered to be synchronized.
+
+ if (synchronizeGender(node, target))
+ synchronizeLastModified(node, target);
+ if (targetType == IResource.FILE) {
+ try {
+ ((File) target).updateMetadataFiles();
+ } catch (CoreException e) {
+ errors.merge(e.getStatus());
+ }
+ }
+ return true;
+ } finally {
+ if (--nextProgress <= 0) {
+ //we have exhausted the current increment, so report progress
+ monitor.worked(1);
+ worked++;
+ if (worked >= halfWay) {
+ //we have passed the current halfway point, so double the
+ //increment and reset the halfway point.
+ currentIncrement *= 2;
+ halfWay += (TOTAL_WORK - halfWay) / 2;
+ }
+ //reset the progress counter to another full increment
+ nextProgress = currentIncrement;
+ }
+ }
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeChunkyInputStream.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeChunkyInputStream.java
new file mode 100644
index 0000000000..b1a50195d7
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeChunkyInputStream.java
@@ -0,0 +1,184 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2014 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.localstore;
+
+import java.io.*;
+
+/**
+ * @see SafeChunkyOutputStream
+ */
+
+public class SafeChunkyInputStream extends InputStream {
+ protected static final int BUFFER_SIZE = 8192;
+ protected byte[] buffer;
+ protected int bufferLength = 0;
+ protected byte[] chunk;
+ protected int chunkLength = 0;
+ protected boolean endOfFile = false;
+ protected InputStream input;
+ protected int nextByteInBuffer = 0;
+ protected int nextByteInChunk = 0;
+
+ public SafeChunkyInputStream(File target) throws IOException {
+ this(target, BUFFER_SIZE);
+ }
+
+ public SafeChunkyInputStream(File target, int bufferSize) throws IOException {
+ input = new FileInputStream(target);
+ buffer = new byte[bufferSize];
+ }
+
+ protected void accumulate(byte[] data, int start, int end) {
+ byte[] result = new byte[chunk.length + end - start];
+ System.arraycopy(chunk, 0, result, 0, chunk.length);
+ System.arraycopy(data, start, result, chunk.length, end - start);
+ chunk = result;
+ chunkLength = chunkLength + end - start;
+ }
+
+ @Override
+ public int available() {
+ return chunkLength - nextByteInChunk;
+ }
+
+ protected void buildChunk() throws IOException {
+ //read buffer loads of data until an entire chunk is accumulated
+ while (true) {
+ if (nextByteInBuffer + ILocalStoreConstants.CHUNK_DELIMITER_SIZE > bufferLength)
+ shiftAndFillBuffer();
+ int end = find(ILocalStoreConstants.END_CHUNK, nextByteInBuffer, bufferLength, true);
+ if (end != -1) {
+ accumulate(buffer, nextByteInBuffer, end);
+ nextByteInBuffer = end + ILocalStoreConstants.CHUNK_DELIMITER_SIZE;
+ return;
+ }
+ accumulate(buffer, nextByteInBuffer, bufferLength);
+ bufferLength = input.read(buffer);
+ nextByteInBuffer = 0;
+ if (bufferLength == -1) {
+ endOfFile = true;
+ return;
+ }
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ input.close();
+ }
+
+ protected boolean compare(byte[] source, byte[] target, int startIndex) {
+ for (int i = 0; i < target.length; i++) {
+ if (source[startIndex] != target[i])
+ return false;
+ startIndex++;
+ }
+ return true;
+ }
+
+ protected int find(byte[] pattern, int startIndex, int endIndex, boolean accumulate) throws IOException {
+ int pos = findByte(pattern[0], startIndex, endIndex);
+ if (pos == -1)
+ return -1;
+ if (pos + ILocalStoreConstants.CHUNK_DELIMITER_SIZE > bufferLength) {
+ if (accumulate)
+ accumulate(buffer, nextByteInBuffer, pos);
+ nextByteInBuffer = pos;
+ pos = 0;
+ shiftAndFillBuffer();
+ }
+ if (compare(buffer, pattern, pos))
+ return pos;
+ return find(pattern, pos + 1, endIndex, accumulate);
+ }
+
+ protected int findByte(byte target, int startIndex, int endIndex) {
+ while (startIndex < endIndex) {
+ if (buffer[startIndex] == target)
+ return startIndex;
+ startIndex++;
+ }
+ return -1;
+ }
+
+ protected void findChunkStart() throws IOException {
+ if (nextByteInBuffer + ILocalStoreConstants.CHUNK_DELIMITER_SIZE > bufferLength)
+ shiftAndFillBuffer();
+ int begin = find(ILocalStoreConstants.BEGIN_CHUNK, nextByteInBuffer, bufferLength, false);
+ if (begin != -1) {
+ nextByteInBuffer = begin + ILocalStoreConstants.CHUNK_DELIMITER_SIZE;
+ return;
+ }
+ bufferLength = input.read(buffer);
+ nextByteInBuffer = 0;
+ if (bufferLength == -1) {
+ resetChunk();
+ endOfFile = true;
+ return;
+ }
+ findChunkStart();
+ }
+
+ @Override
+ public int read() throws IOException {
+ if (endOfFile)
+ return -1;
+ // if there are bytes left in the chunk, return the first available
+ if (nextByteInChunk < chunkLength)
+ return chunk[nextByteInChunk++] & 0xFF;
+ // Otherwise the chunk is empty so clear the current one, get the next
+ // one and recursively call read. Need to recur as the chunk may be
+ // real but empty.
+ resetChunk();
+ findChunkStart();
+ if (endOfFile)
+ return -1;
+ buildChunk();
+ refineChunk();
+ return read();
+ }
+
+ /**
+ * Skip over any begin chunks in the current chunk. This could be optimized
+ * to skip at the same time as we are scanning the buffer.
+ */
+ protected void refineChunk() {
+ int start = chunkLength - ILocalStoreConstants.CHUNK_DELIMITER_SIZE;
+ if (start < 0)
+ return;
+ for (int i = start; i >= 0; i--) {
+ if (compare(chunk, ILocalStoreConstants.BEGIN_CHUNK, i)) {
+ nextByteInChunk = i + ILocalStoreConstants.CHUNK_DELIMITER_SIZE;
+ return;
+ }
+ }
+ }
+
+ protected void resetChunk() {
+ chunk = new byte[0];
+ chunkLength = 0;
+ nextByteInChunk = 0;
+ }
+
+ protected void shiftAndFillBuffer() throws IOException {
+ int length = bufferLength - nextByteInBuffer;
+ System.arraycopy(buffer, nextByteInBuffer, buffer, 0, length);
+ nextByteInBuffer = 0;
+ bufferLength = length;
+ int read = input.read(buffer, bufferLength, buffer.length - bufferLength);
+ if (read != -1)
+ bufferLength += read;
+ else {
+ resetChunk();
+ endOfFile = true;
+ }
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeChunkyOutputStream.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeChunkyOutputStream.java
new file mode 100644
index 0000000000..de904b7d49
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeChunkyOutputStream.java
@@ -0,0 +1,81 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2014 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * James Blackburn (Broadcom Corp.) - ongoing development
+ *******************************************************************************/
+package org.eclipse.core.internal.localstore;
+
+import java.io.*;
+import org.eclipse.core.internal.utils.FileUtil;
+
+/**
+ * Appends data, in chunks, to a file. Each chunk is defined by the moment
+ * the stream is opened (created) and a call to #succeed is made. It is
+ * necessary to use the SafeChunkyInputStream to read its
+ * contents back. The user of this class does not need to know explicitly about
+ * its chunk implementation.
+ * It is only an implementation detail. What really matters to the outside
+ * world is that it tries to keep the file data consistent.
+ * If some data becomes corrupted while writing or later, upon reading
+ * the file, the chunk that contains the corrupted data is skipped.
+ *
+ * Because of this class purpose (keep data consistent), it is important that the
+ * user only calls #succeed when the chunk of data is successfully
+ * written. After this call, the user can continue writing data to the file and it
+ * will not be considered related to the previous chunk. So, if this data is
+ * corrupted, the previous one is still safe.
+ *
+ * @see SafeChunkyInputStream
+ */
+public class SafeChunkyOutputStream extends FilterOutputStream {
+ protected String filePath;
+ protected boolean isOpen;
+
+ public SafeChunkyOutputStream(File target) throws IOException {
+ this(target.getAbsolutePath());
+ }
+
+ public SafeChunkyOutputStream(String filePath) throws IOException {
+ super(new BufferedOutputStream(new FileOutputStream(filePath, true)));
+ this.filePath = filePath;
+ isOpen = true;
+ beginChunk();
+ }
+
+ protected void beginChunk() throws IOException {
+ write(ILocalStoreConstants.BEGIN_CHUNK);
+ }
+
+ protected void endChunk() throws IOException {
+ write(ILocalStoreConstants.END_CHUNK);
+ }
+
+ protected void open() throws IOException {
+ out = new BufferedOutputStream(new FileOutputStream(filePath, true));
+ isOpen = true;
+ beginChunk();
+ }
+
+ public void succeed() throws IOException {
+ try {
+ endChunk();
+ close();
+ } finally {
+ isOpen = false;
+ FileUtil.safeClose(this);
+ }
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ if (!isOpen)
+ open();
+ super.write(b);
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeFileInputStream.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeFileInputStream.java
new file mode 100644
index 0000000000..2cb00d4fc3
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeFileInputStream.java
@@ -0,0 +1,53 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.localstore;
+
+import java.io.*;
+
+/**
+ * Given a target and a temporary locations, it tries to read the contents
+ * from the target. If a file does not exist at the target location, it tries
+ * to read the contents from the temporary location.
+ *
+ * @see SafeFileOutputStream
+ */
+public class SafeFileInputStream extends FilterInputStream {
+ protected static final String EXTENSION = ".bak"; //$NON-NLS-1$
+ private static final int DEFAUT_BUFFER_SIZE = 2048;
+
+ public SafeFileInputStream(File file) throws IOException {
+ this(file.getAbsolutePath(), null);
+ }
+
+ /**
+ * If targetPath is null, the file will be created in the default-temporary directory.
+ */
+ public SafeFileInputStream(String targetPath, String tempPath) throws IOException {
+ super(getInputStream(targetPath, tempPath, DEFAUT_BUFFER_SIZE));
+ }
+
+ /**
+ * If targetPath is null, the file will be created in the default-temporary directory.
+ */
+ public SafeFileInputStream(String targetPath, String tempPath, int bufferSize) throws IOException {
+ super(getInputStream(targetPath, tempPath, bufferSize));
+ }
+
+ private static InputStream getInputStream(String targetPath, String tempPath, int bufferSize) throws IOException {
+ File target = new File(targetPath);
+ if (!target.exists()) {
+ if (tempPath == null)
+ tempPath = target.getAbsolutePath() + EXTENSION;
+ target = new File(tempPath);
+ }
+ return new BufferedInputStream(new FileInputStream(target), bufferSize);
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeFileOutputStream.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeFileOutputStream.java
new file mode 100644
index 0000000000..86532387df
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeFileOutputStream.java
@@ -0,0 +1,141 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2014 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * James Blackburn (Broadcom Corp.) - ongoing development
+ *******************************************************************************/
+package org.eclipse.core.internal.localstore;
+
+import java.io.*;
+import org.eclipse.core.internal.utils.FileUtil;
+
+/**
+ * This class should be used when there's a file already in the
+ * destination and we don't want to lose its contents if a
+ * failure writing this stream happens.
+ * Basically, the new contents are written to a temporary location.
+ * If everything goes OK, it is moved to the right place.
+ */
+public class SafeFileOutputStream extends OutputStream {
+ protected File temp;
+ protected File target;
+ protected OutputStream output;
+ protected boolean failed;
+ protected static final String EXTENSION = ".bak"; //$NON-NLS-1$
+
+ /**
+ * Creates an output stream on a file at the given location
+ * @param file The file to be written to
+ */
+ public SafeFileOutputStream(File file) throws IOException {
+ this(file.getAbsolutePath(), null);
+ }
+
+ /**
+ * Creates an output stream on a file at the given location
+ * @param targetPath The file to be written to
+ * @param tempPath The temporary location to use, or null to
+ * use the same location as the target path but with a different extension.
+ */
+ public SafeFileOutputStream(String targetPath, String tempPath) throws IOException {
+ failed = false;
+ target = new File(targetPath);
+ createTempFile(tempPath);
+ if (!target.exists()) {
+ if (!temp.exists()) {
+ output = new BufferedOutputStream(new FileOutputStream(target));
+ return;
+ }
+ // If we do not have a file at target location, but we do have at temp location,
+ // it probably means something wrong happened the last time we tried to write it.
+ // So, try to recover the backup file. And, if successful, write the new one.
+ copy(temp, target);
+ }
+ output = new BufferedOutputStream(new FileOutputStream(temp));
+ }
+
+ @Override
+ public void close() throws IOException {
+ try {
+ output.close();
+ } catch (IOException e) {
+ failed = true;
+ throw e; // rethrow
+ }
+ if (failed)
+ temp.delete();
+ else
+ commit();
+ }
+
+ protected void commit() throws IOException {
+ if (!temp.exists())
+ return;
+ target.delete();
+ copy(temp, target);
+ temp.delete();
+ }
+
+ protected void copy(File sourceFile, File destinationFile) throws IOException {
+ if (!sourceFile.exists())
+ return;
+ if (sourceFile.renameTo(destinationFile))
+ return;
+ InputStream source = null;
+ OutputStream destination = null;
+ try {
+ source = new BufferedInputStream(new FileInputStream(sourceFile));
+ destination = new BufferedOutputStream(new FileOutputStream(destinationFile));
+ transferStreams(source, destination);
+ destination.close();
+ } finally {
+ FileUtil.safeClose(source);
+ FileUtil.safeClose(destination);
+ }
+ }
+
+ protected void createTempFile(String tempPath) {
+ if (tempPath == null)
+ tempPath = target.getAbsolutePath() + EXTENSION;
+ temp = new File(tempPath);
+ }
+
+ @Override
+ public void flush() throws IOException {
+ try {
+ output.flush();
+ } catch (IOException e) {
+ failed = true;
+ throw e; // rethrow
+ }
+ }
+
+ public String getTempFilePath() {
+ return temp.getAbsolutePath();
+ }
+
+ protected void transferStreams(InputStream source, OutputStream destination) throws IOException {
+ byte[] buffer = new byte[8192];
+ while (true) {
+ int bytesRead = source.read(buffer);
+ if (bytesRead == -1)
+ break;
+ destination.write(buffer, 0, bytesRead);
+ }
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ try {
+ output.write(b);
+ } catch (IOException e) {
+ failed = true;
+ throw e; // rethrow
+ }
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/UnifiedTree.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/UnifiedTree.java
new file mode 100644
index 0000000000..70039334c1
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/UnifiedTree.java
@@ -0,0 +1,573 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2015 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * Martin Oberhuber (Wind River) - [105554] handle cyclic symbolic links
+ * Martin Oberhuber (Wind River) - [232426] shared prefix histories for symlinks
+ * Serge Beauchamp (Freescale Semiconductor) - [252996] add resource filtering
+ * Martin Oberhuber (Wind River) - [292267] OutOfMemoryError due to leak in UnifiedTree
+ * James Blackburn (Broadcom Corp.) - ongoing development
+ *******************************************************************************/
+package org.eclipse.core.internal.localstore;
+
+import java.io.IOException;
+import java.util.*;
+import java.util.regex.Pattern;
+import org.eclipse.core.filesystem.*;
+import org.eclipse.core.internal.refresh.RefreshJob;
+import org.eclipse.core.internal.resources.*;
+import org.eclipse.core.internal.utils.Queue;
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.*;
+import org.eclipse.core.runtime.jobs.Job;
+
+/**
+ * Represents the workspace's tree merged with the file system's tree.
+ */
+public class UnifiedTree {
+ /** special node to mark the separation of a node's children */
+ protected static final UnifiedTreeNode childrenMarker = new UnifiedTreeNode(null, null, null, null, false);
+
+ private static final Iterator EMPTY_ITERATOR = Collections.EMPTY_LIST.iterator();
+
+ /** special node to mark the beginning of a level in the tree */
+ protected static final UnifiedTreeNode levelMarker = new UnifiedTreeNode(null, null, null, null, false);
+
+ private static final IFileInfo[] NO_CHILDREN = new IFileInfo[0];
+
+ /** Singleton to indicate no local children */
+ private static final IResource[] NO_RESOURCES = new IResource[0];
+
+ /**
+ * True if the level of the children of the current node are valid according
+ * to the requested refresh depth, false otherwise
+ */
+ protected boolean childLevelValid = false;
+
+ /** an IFileTree which can be used to build a unified tree*/
+ protected IFileTree fileTree = null;
+
+ /** Spare node objects available for reuse */
+ protected ArrayList freeNodes = new ArrayList();
+ /** tree's actual level */
+ protected int level;
+ /** our queue */
+ protected Queue queue;
+
+ /** path prefixes for checking symbolic link cycles */
+ protected PrefixPool pathPrefixHistory, rootPathHistory;
+
+ /** tree's root */
+ protected IResource root;
+
+ /**
+ * The root must only be a file or a folder.
+ */
+ public UnifiedTree(IResource root) {
+ setRoot(root);
+ }
+
+ /**
+ * Pass in a a root for the tree, a file tree containing all of the entries for this
+ * tree and a flag indicating whether the UnifiedTree should consult the fileTree where
+ * possible for entries
+ * @param root
+ * @param fileTree
+ */
+ public UnifiedTree(IResource root, IFileTree fileTree) {
+ this(root);
+ this.fileTree = fileTree;
+ }
+
+ public void accept(IUnifiedTreeVisitor visitor) throws CoreException {
+ accept(visitor, IResource.DEPTH_INFINITE);
+ }
+
+ /**
+ * Performs a breadth-first traversal of the unified tree, passing each
+ * node to the provided visitor.
+ */
+ public void accept(IUnifiedTreeVisitor visitor, int depth) throws CoreException {
+ Assert.isNotNull(root);
+ initializeQueue();
+ setLevel(0, depth);
+ while (!queue.isEmpty()) {
+ UnifiedTreeNode node = queue.remove();
+ if (isChildrenMarker(node))
+ continue;
+ if (isLevelMarker(node)) {
+ if (!setLevel(getLevel() + 1, depth))
+ break;
+ continue;
+ }
+ if (visitor.visit(node))
+ addNodeChildrenToQueue(node);
+ else
+ removeNodeChildrenFromQueue(node);
+ //allow reuse of the node, but don't let the freeNodes list grow infinitely
+ if (freeNodes.size() < 32767) {
+ //free memory-consuming elements of the node for garbage collection
+ node.releaseForGc();
+ freeNodes.add(node);
+ }
+ //else, the whole node will be garbage collected since there is no
+ //reference to it any more.
+ }
+ }
+
+ protected void addChildren(UnifiedTreeNode node) {
+ Resource parent = (Resource) node.getResource();
+
+ // is there a possibility to have children?
+ int parentType = parent.getType();
+ if (parentType == IResource.FILE && !node.isFolder())
+ return;
+
+ //don't refresh resources in closed or non-existent projects
+ if (!parent.getProject().isAccessible())
+ return;
+
+ // get the list of resources in the file system
+ // don't ask for local children if we know it doesn't exist locally
+ IFileInfo[] list = node.existsInFileSystem() ? getLocalList(node) : NO_CHILDREN;
+ int localIndex = 0;
+
+ // See if the children of this resource have been computed before
+ ResourceInfo resourceInfo = parent.getResourceInfo(false, false);
+ int flags = parent.getFlags(resourceInfo);
+ boolean unknown = ResourceInfo.isSet(flags, ICoreConstants.M_CHILDREN_UNKNOWN);
+
+ // get the list of resources in the workspace
+ if (!unknown && (parentType == IResource.FOLDER || parentType == IResource.PROJECT) && parent.exists(flags, true)) {
+ IResource target = null;
+ UnifiedTreeNode child = null;
+ IResource[] members;
+ try {
+ members = ((IContainer) parent).members(IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS | IContainer.INCLUDE_HIDDEN);
+ } catch (CoreException e) {
+ members = NO_RESOURCES;
+ }
+ int workspaceIndex = 0;
+ //iterate simultaneously over file system and workspace members
+ while (workspaceIndex < members.length) {
+ target = members[workspaceIndex];
+ String name = target.getName();
+ IFileInfo localInfo = localIndex < list.length ? list[localIndex] : null;
+ int comp = localInfo != null ? name.compareTo(localInfo.getName()) : -1;
+ //special handling for linked resources
+ if (target.isLinked()) {
+ //child will be null if location is undefined
+ child = createChildForLinkedResource(target);
+ workspaceIndex++;
+ //if there is a matching local file, skip it - it will be blocked by the linked resource
+ if (comp == 0)
+ localIndex++;
+ } else if (comp == 0) {
+ // resource exists in workspace and file system --> localInfo is non-null
+ //create workspace-only node for symbolic link that creates a cycle
+ if (localInfo.getAttribute(EFS.ATTRIBUTE_SYMLINK) && localInfo.isDirectory() && isRecursiveLink(node.getStore(), localInfo))
+ child = createNode(target, null, null, true);
+ else
+ child = createNode(target, null, localInfo, true);
+ localIndex++;
+ workspaceIndex++;
+ } else if (comp > 0) {
+ // resource exists only in file system
+ //don't create a node for symbolic links that create a cycle
+ if (localInfo.getAttribute(EFS.ATTRIBUTE_SYMLINK) && localInfo.isDirectory() && isRecursiveLink(node.getStore(), localInfo))
+ child = null;
+ else
+ child = createChildNodeFromFileSystem(node, localInfo);
+ localIndex++;
+ } else {
+ // resource exists only in the workspace
+ child = createNode(target, null, null, true);
+ workspaceIndex++;
+ }
+ if (child != null)
+ addChildToTree(node, child);
+ }
+ }
+
+ /* process any remaining resource from the file system */
+ addChildrenFromFileSystem(node, list, localIndex);
+
+ /* Mark the children as now known */
+ if (unknown) {
+ // Don't open the info - we might not be inside a workspace-modifying operation
+ resourceInfo = parent.getResourceInfo(false, false);
+ if (resourceInfo != null)
+ resourceInfo.clear(ICoreConstants.M_CHILDREN_UNKNOWN);
+ }
+
+ /* if we added children, add the childMarker separator */
+ if (node.getFirstChild() != null)
+ addChildrenMarker();
+ }
+
+ protected void addChildrenFromFileSystem(UnifiedTreeNode node, IFileInfo[] childInfos, int index) {
+ if (childInfos == null)
+ return;
+ for (int i = index; i < childInfos.length; i++) {
+ IFileInfo info = childInfos[i];
+ //don't create a node for symbolic links that create a cycle
+ if (!info.getAttribute(EFS.ATTRIBUTE_SYMLINK) || !info.isDirectory() || !isRecursiveLink(node.getStore(), info))
+ addChildToTree(node, createChildNodeFromFileSystem(node, info));
+ }
+ }
+
+ protected void addChildrenMarker() {
+ addElementToQueue(childrenMarker);
+ }
+
+ protected void addChildToTree(UnifiedTreeNode node, UnifiedTreeNode child) {
+ if (node.getFirstChild() == null)
+ node.setFirstChild(child);
+ addElementToQueue(child);
+ }
+
+ protected void addElementToQueue(UnifiedTreeNode target) {
+ queue.add(target);
+ }
+
+ protected void addNodeChildrenToQueue(UnifiedTreeNode node) {
+ /* if the first child is not null we already added the children */
+ /* If the children won't be at a valid level for the refresh depth, don't bother adding them */
+ if (!childLevelValid || node.getFirstChild() != null)
+ return;
+ addChildren(node);
+ if (queue.isEmpty())
+ return;
+ //if we're about to change levels, then the children just added
+ //are the last nodes for their level, so add a level marker to the queue
+ UnifiedTreeNode nextNode = queue.peek();
+ if (isChildrenMarker(nextNode))
+ queue.remove();
+ nextNode = queue.peek();
+ if (isLevelMarker(nextNode))
+ addElementToQueue(levelMarker);
+ }
+
+ protected void addRootToQueue() {
+ //don't refresh in closed projects
+ if (!root.getProject().isAccessible())
+ return;
+ IFileStore store = ((Resource) root).getStore();
+ IFileInfo fileInfo = fileTree != null ? fileTree.getFileInfo(store) : store.fetchInfo();
+ UnifiedTreeNode node = createNode(root, store, fileInfo, root.exists());
+ if (node.existsInFileSystem() || node.existsInWorkspace())
+ addElementToQueue(node);
+ }
+
+ /**
+ * Creates a tree node for a resource that is linked in a different file system location.
+ */
+ protected UnifiedTreeNode createChildForLinkedResource(IResource target) {
+ IFileStore store = ((Resource) target).getStore();
+ return createNode(target, store, store.fetchInfo(), true);
+ }
+
+ /**
+ * Creates a child node for a location in the file system. Does nothing and returns null if the location does not correspond to a valid file/folder.
+ */
+ protected UnifiedTreeNode createChildNodeFromFileSystem(UnifiedTreeNode parent, IFileInfo info) {
+ IPath childPath = parent.getResource().getFullPath().append(info.getName());
+ int type = info.isDirectory() ? IResource.FOLDER : IResource.FILE;
+ IResource target = getWorkspace().newResource(childPath, type);
+ return createNode(target, null, info, target.exists());
+ }
+
+ /**
+ * Factory method for creating a node for this tree. If the file exists on
+ * disk, either the parent store or child store can be provided. Providing
+ * only the parent store avoids creation of the child store in cases where
+ * it is not needed. The store object is only needed for directories for
+ * simple file system traversals, so this avoids creating store objects
+ * for all files.
+ */
+ protected UnifiedTreeNode createNode(IResource resource, IFileStore store, IFileInfo info, boolean existsWorkspace) {
+ //first check for reusable objects
+ UnifiedTreeNode node = null;
+ int size = freeNodes.size();
+ if (size > 0) {
+ node = freeNodes.remove(size - 1);
+ node.reuse(this, resource, store, info, existsWorkspace);
+ return node;
+ }
+ //none available, so create a new one
+ return new UnifiedTreeNode(this, resource, store, info, existsWorkspace);
+ }
+
+ protected Iterator getChildren(UnifiedTreeNode node) {
+ /* if first child is null we need to add node's children to queue */
+ if (node.getFirstChild() == null)
+ addNodeChildrenToQueue(node);
+
+ /* if the first child is still null, the node does not have any children */
+ if (node.getFirstChild() == null)
+ return EMPTY_ITERATOR;
+
+ /* get the index of the first child */
+ int index = queue.indexOf(node.getFirstChild());
+
+ /* if we do not have children, just return an empty enumeration */
+ if (index == -1)
+ return EMPTY_ITERATOR;
+
+ /* create an enumeration with node's children */
+ List result = new ArrayList(10);
+ while (true) {
+ UnifiedTreeNode child = queue.elementAt(index);
+ if (isChildrenMarker(child))
+ break;
+ result.add(child);
+ index = queue.increment(index);
+ }
+ return result.iterator();
+ }
+
+ protected int getLevel() {
+ return level;
+ }
+
+ protected IFileInfo[] getLocalList(UnifiedTreeNode node) {
+ try {
+ final IFileStore store = node.getStore();
+ IFileInfo[] list;
+ if (fileTree != null && (fileTree.getTreeRoot().equals(store) || fileTree.getTreeRoot().isParentOf(store)))
+ list = fileTree.getChildInfos(store);
+ else
+ list = store.childInfos(EFS.NONE, null);
+
+ if (list == null || list.length == 0)
+ return NO_CHILDREN;
+ list = ((Resource) node.getResource()).filterChildren(list, false);
+ int size = list.length;
+ if (size > 1)
+ quickSort(list, 0, size - 1);
+ return list;
+ } catch (CoreException e) {
+ //treat failure to access the directory as a non-existent directory
+ return NO_CHILDREN;
+ }
+ }
+
+ protected Workspace getWorkspace() {
+ return (Workspace) root.getWorkspace();
+ }
+
+ protected void initializeQueue() {
+ //initialize the queue
+ if (queue == null)
+ queue = new Queue(100, false);
+ else
+ queue.reset();
+ //initialize the free nodes list
+ if (freeNodes == null)
+ freeNodes = new ArrayList(100);
+ else
+ freeNodes.clear();
+ addRootToQueue();
+ addElementToQueue(levelMarker);
+ }
+
+ protected boolean isChildrenMarker(UnifiedTreeNode node) {
+ return node == childrenMarker;
+ }
+
+ protected boolean isLevelMarker(UnifiedTreeNode node) {
+ return node == levelMarker;
+ }
+
+ private static class PatternHolder {
+ //Initialize-on-demand Holder class to avoid compiling Pattern if never needed
+ //Pattern: A UNIX or Windows relative path that just points backward
+ private static final String REGEX = Platform.getOS().equals(Platform.OS_WIN32) ? "\\.[.\\\\]*" : "\\.[./]*"; //$NON-NLS-1$ //$NON-NLS-2$
+ public static final Pattern TRIVIAL_SYMLINK_PATTERN = Pattern.compile(REGEX);
+ }
+
+ /**
+ * Initialize history stores for symbolic links.
+ * This may be done when starting a visitor, or later on demand.
+ */
+ protected void initLinkHistoriesIfNeeded() {
+ if (pathPrefixHistory == null) {
+ //Bug 232426: Check what life cycle we need for the histories
+ Job job = Job.getJobManager().currentJob();
+ if (job instanceof RefreshJob) {
+ //we are running from the RefreshJob: use the path history of the job
+ RefreshJob refreshJob = (RefreshJob) job;
+ pathPrefixHistory = refreshJob.getPathPrefixHistory();
+ rootPathHistory = refreshJob.getRootPathHistory();
+ } else {
+ //Local Histories
+ pathPrefixHistory = new PrefixPool(20);
+ rootPathHistory = new PrefixPool(20);
+ }
+ }
+ if (rootPathHistory.size() == 0) {
+ //add current root to history
+ IFileStore rootStore = ((Resource) root).getStore();
+ try {
+ java.io.File rootFile = rootStore.toLocalFile(EFS.NONE, null);
+ if (rootFile != null) {
+ IPath rootProjPath = root.getProject().getLocation();
+ if (rootProjPath != null) {
+ try {
+ java.io.File rootProjFile = new java.io.File(rootProjPath.toOSString());
+ rootPathHistory.insertShorter(rootProjFile.getCanonicalPath() + '/');
+ } catch (IOException ioe) {
+ /*ignore here*/
+ }
+ }
+ rootPathHistory.insertShorter(rootFile.getCanonicalPath() + '/');
+ }
+ } catch (CoreException e) {
+ /*ignore*/
+ } catch (IOException e) {
+ /*ignore*/
+ }
+ }
+ }
+
+ /**
+ * Check if the given child represents a recursive symbolic link.
+ *
+ * On remote EFS stores, this check is not exhaustive and just
+ * finds trivial recursive symbolic links pointing up in the tree.
+ *
+ * On local stores, where {@link java.io.File#getCanonicalPath()}
+ * is available, the test is exhaustive but may also find some
+ * false positives with transitive symbolic links. This may lead
+ * to suppressing duplicates of already known resources in the
+ * tree, but it will never lead to not finding a resource at
+ * all. See bug 105554 for details.
+ *
+ * @param parentStore EFS IFileStore representing the parent folder
+ * @param localInfo child representing a symbolic link
+ * @return true if the given child represents a
+ * recursive symbolic link.
+ */
+ private boolean isRecursiveLink(IFileStore parentStore, IFileInfo localInfo) {
+ //Try trivial pattern first - works also on remote EFS stores
+ String linkTarget = localInfo.getStringAttribute(EFS.ATTRIBUTE_LINK_TARGET);
+ if (linkTarget != null && PatternHolder.TRIVIAL_SYMLINK_PATTERN.matcher(linkTarget).matches()) {
+ return true;
+ }
+ //Need canonical paths to check all other possibilities
+ try {
+ java.io.File parentFile = parentStore.toLocalFile(EFS.NONE, null);
+ //If this store cannot be represented as a local file, there is nothing we can do
+ //In the future, we could try to resolve the link target
+ //against the remote file system to do more checks.
+ if (parentFile == null)
+ return false;
+ //get canonical path for both child and parent
+ java.io.File childFile = new java.io.File(parentFile, localInfo.getName());
+ String parentPath = parentFile.getCanonicalPath() + '/';
+ String childPath = childFile.getCanonicalPath() + '/';
+ //get or instantiate the prefix and root path histories.
+ //Might be done earlier - for now, do it on demand.
+ initLinkHistoriesIfNeeded();
+ //insert the parent for checking loops
+ pathPrefixHistory.insertLonger(parentPath);
+ if (pathPrefixHistory.containsAsPrefix(childPath)) {
+ //found a potential loop: is it spanning up a new tree?
+ if (!rootPathHistory.insertShorter(childPath)) {
+ //not spanning up a new tree, so it is a real loop.
+ return true;
+ }
+ } else if (rootPathHistory.hasPrefixOf(childPath)) {
+ //child points into a different portion of the tree that we visited already before, or will certainly visit.
+ //This does not introduce a loop yet, but introduces duplicate resources.
+ //TODO Ideally, such duplicates should be modelled as linked resources. See bug 105534
+ return false;
+ } else {
+ //child neither introduces a loop nor points to a known tree.
+ //It probably spans up a new tree of potential prefixes.
+ rootPathHistory.insertShorter(childPath);
+ }
+ } catch (IOException e) {
+ //ignore
+ } catch (CoreException e) {
+ //ignore
+ }
+ return false;
+ }
+
+ protected boolean isValidLevel(int currentLevel, int depth) {
+ switch (depth) {
+ case IResource.DEPTH_INFINITE :
+ return true;
+ case IResource.DEPTH_ONE :
+ return currentLevel <= 1;
+ case IResource.DEPTH_ZERO :
+ return currentLevel == 0;
+ default :
+ return currentLevel + 1000 <= depth;
+ }
+ }
+
+ /**
+ * Sorts the given array of strings in place. This is
+ * not using the sorting framework to avoid casting overhead.
+ */
+ protected void quickSort(IFileInfo[] infos, int left, int right) {
+ int originalLeft = left;
+ int originalRight = right;
+ IFileInfo mid = infos[(left + right) / 2];
+ do {
+ while (mid.compareTo(infos[left]) > 0)
+ left++;
+ while (infos[right].compareTo(mid) > 0)
+ right--;
+ if (left <= right) {
+ IFileInfo tmp = infos[left];
+ infos[left] = infos[right];
+ infos[right] = tmp;
+ left++;
+ right--;
+ }
+ } while (left <= right);
+ if (originalLeft < right)
+ quickSort(infos, originalLeft, right);
+ if (left < originalRight)
+ quickSort(infos, left, originalRight);
+ return;
+ }
+
+ /**
+ * Remove from the last element of the queue to the first child of the
+ * given node.
+ */
+ protected void removeNodeChildrenFromQueue(UnifiedTreeNode node) {
+ UnifiedTreeNode first = node.getFirstChild();
+ if (first == null)
+ return;
+ while (true) {
+ if (first.equals(queue.removeTail()))
+ break;
+ }
+ node.setFirstChild(null);
+ }
+
+ /**
+ * Increases the current tree level by one. Returns true if the new
+ * level is still valid for the given depth
+ */
+ protected boolean setLevel(int newLevel, int depth) {
+ level = newLevel;
+ childLevelValid = isValidLevel(level + 1, depth);
+ return isValidLevel(level, depth);
+ }
+
+ private void setRoot(IResource root) {
+ this.root = root;
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/UnifiedTreeNode.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/UnifiedTreeNode.java
new file mode 100644
index 0000000000..b5ca498799
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/UnifiedTreeNode.java
@@ -0,0 +1,149 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2012 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * Martin Oberhuber (Wind River) - [292267] OutOfMemoryError due to leak in UnifiedTree
+ *******************************************************************************/
+package org.eclipse.core.internal.localstore;
+
+import java.util.Iterator;
+import org.eclipse.core.filesystem.*;
+import org.eclipse.core.internal.resources.Resource;
+import org.eclipse.core.resources.IResource;
+
+/**
+ * A node in a {@link UnifiedTree}. A node usually represents a file/folder
+ * in the workspace, the file system, or both. There are also special node
+ * instances to act as child and level markers in the tree.
+ */
+public class UnifiedTreeNode implements ILocalStoreConstants {
+ protected UnifiedTreeNode child;
+ protected boolean existsWorkspace;
+ protected IFileInfo fileInfo;
+ protected IResource resource;
+ protected IFileStore store;
+ protected UnifiedTree tree;
+
+ public UnifiedTreeNode(UnifiedTree tree, IResource resource, IFileStore store, IFileInfo fileInfo, boolean existsWorkspace) {
+ this.tree = tree;
+ this.resource = resource;
+ this.store = store;
+ this.fileInfo = fileInfo;
+ this.existsWorkspace = existsWorkspace;
+ }
+
+ public boolean existsInFileSystem() {
+ return fileInfo != null && fileInfo.exists();
+ }
+
+ /**
+ * Returns true if an I/O error was encountered while accessing
+ * the file or the directory in the file system.
+ */
+ public boolean isErrorInFileSystem() {
+ return fileInfo != null && fileInfo.getError() != IFileInfo.NONE;
+ }
+
+ public boolean existsInWorkspace() {
+ return existsWorkspace;
+ }
+
+ /**
+ * Returns an iterator of this node's children.
+ */
+ public Iterator getChildren() {
+ return tree.getChildren(this);
+ }
+
+ protected UnifiedTreeNode getFirstChild() {
+ return child;
+ }
+
+ public long getLastModified() {
+ return fileInfo == null ? 0 : fileInfo.getLastModified();
+ }
+
+ public int getLevel() {
+ return tree.getLevel();
+ }
+
+ /**
+ * Gets the name of this node in the local file system.
+ * @return Returns a String
+ */
+ public String getLocalName() {
+ return fileInfo == null ? null : fileInfo.getName();
+ }
+
+ public IResource getResource() {
+ return resource;
+ }
+
+ /**
+ * Returns the local store of this resource. May be null.
+ */
+ public IFileStore getStore() {
+ //initialize store lazily, because it is not always needed
+ if (store == null)
+ store = ((Resource) resource).getStore();
+ return store;
+ }
+
+ public boolean isFolder() {
+ return fileInfo == null ? false : fileInfo.isDirectory();
+ }
+
+ public boolean isSymbolicLink() {
+ return fileInfo == null ? false : fileInfo.getAttribute(EFS.ATTRIBUTE_SYMLINK);
+ }
+
+ public void removeChildrenFromTree() {
+ tree.removeNodeChildrenFromQueue(this);
+ }
+
+ /**
+ * Reuses this object by assigning all new values for the fields.
+ */
+ public void reuse(UnifiedTree aTree, IResource aResource, IFileStore aStore, IFileInfo info, boolean existsInWorkspace) {
+ this.tree = aTree;
+ this.child = null;
+ this.resource = aResource;
+ this.store = aStore;
+ this.fileInfo = info;
+ this.existsWorkspace = existsInWorkspace;
+ }
+
+ /**
+ * Releases elements that won't be needed any more for garbage collection.
+ * Should be called before adding a node to the free list.
+ */
+ public void releaseForGc() {
+ this.child = null;
+ this.resource = null;
+ this.store = null;
+ this.fileInfo = null;
+ }
+
+ public void setExistsWorkspace(boolean exists) {
+ this.existsWorkspace = exists;
+ }
+
+ protected void setFirstChild(UnifiedTreeNode child) {
+ this.child = child;
+ }
+
+ public void setResource(IResource resource) {
+ this.resource = resource;
+ }
+
+ @Override
+ public String toString() {
+ String s = resource == null ? "null" : resource.getFullPath().toString(); //$NON-NLS-1$
+ return "Node: " + s; //$NON-NLS-1$
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/properties/IPropertyManager.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/properties/IPropertyManager.java
new file mode 100644
index 0000000000..69bae55176
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/properties/IPropertyManager.java
@@ -0,0 +1,75 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.properties;
+
+import java.util.Map;
+import org.eclipse.core.internal.resources.IManager;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.QualifiedName;
+
+public interface IPropertyManager extends IManager {
+ /**
+ * Closes the property store for a resource
+ *
+ * @param target The resource to close the property store for
+ * @exception CoreException
+ */
+ public void closePropertyStore(IResource target) throws CoreException;
+
+ /**
+ * Copy all the properties of one resource to another. Both resources
+ * must have a property store available.
+ */
+ public void copy(IResource source, IResource destination, int depth) throws CoreException;
+
+ /**
+ * Deletes all properties for the given resource and its children.
+ *
+ * The subtree under the given resource is traversed to the supplied depth.
+ *
+ * @param target
+ * @param depth
+ * @exception CoreException
+ */
+ public void deleteProperties(IResource target, int depth) throws CoreException;
+
+ /**
+ * The resource is being deleted so permanently erase its properties.
+ */
+ public void deleteResource(IResource target) throws CoreException;
+
+ /**
+ * Returns the value of the identified property on the given resource as
+ * maintained by this store.
+ *
+ * The qualifier part of the property name must be the unique identifier
+ * of the declaring plug-in (e.g. "com.example.plugin").
+ *
+ */
+ public String getProperty(IResource target, QualifiedName name) throws CoreException;
+
+ /**
+ * Sets the value of the identified property on the given resource.
+ *
+ * The qualifier part of the property name must be the unique identifier
+ * of the declaring plug-in (e.g. "com.example.plugin").
+ *
+ */
+ public void setProperty(IResource target, QualifiedName name, String value) throws CoreException;
+
+ /**
+ * Returns a map ( value: String>) containing
+ * all properties defined for the given resource. In case no properties can
+ * be found, returns an empty map.
+ */
+ public Map getProperties(IResource resource) throws CoreException;
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/properties/PropertyBucket.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/properties/PropertyBucket.java
new file mode 100644
index 0000000000..8c6f13f763
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/properties/PropertyBucket.java
@@ -0,0 +1,355 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2015 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.properties;
+
+import java.io.*;
+import java.util.*;
+import org.eclipse.core.internal.localstore.Bucket;
+import org.eclipse.core.internal.resources.ResourceException;
+import org.eclipse.core.internal.utils.Messages;
+import org.eclipse.core.resources.IResourceStatus;
+import org.eclipse.core.runtime.*;
+import org.eclipse.osgi.util.NLS;
+
+public class PropertyBucket extends Bucket {
+ public static class PropertyEntry extends Entry {
+
+ private final static Comparator COMPARATOR = new Comparator() {
+ @Override
+ public int compare(String[] o1, String[] o2) {
+ int qualifierComparison = o1[0].compareTo(o2[0]);
+ return qualifierComparison != 0 ? qualifierComparison : o1[1].compareTo(o2[1]);
+ }
+ };
+ private static final String[][] EMPTY_DATA = new String[0][];
+ /**
+ * value is a String[][] of {{propertyKey.qualifier, propertyKey.localName, propertyValue}}
+ */
+ private String[][] value;
+
+ /**
+ * Deletes the property with the given name, and returns the result array. Returns the original
+ * array if the property to be deleted could not be found. Returns null if the property was found
+ * and the original array had size 1 (instead of a zero-length array).
+ */
+ static String[][] delete(String[][] existing, QualifiedName propertyName) {
+ // a size-1 array is a special case
+ if (existing.length == 1)
+ return (existing[0][0].equals(propertyName.getQualifier()) && existing[0][1].equals(propertyName.getLocalName())) ? null : existing;
+ // find the guy to delete
+ int deletePosition = search(existing, propertyName);
+ if (deletePosition < 0)
+ // not found, nothing to delete
+ return existing;
+ String[][] newValue = new String[existing.length - 1][];
+ if (deletePosition > 0)
+ // copy elements preceding the one to be removed
+ System.arraycopy(existing, 0, newValue, 0, deletePosition);
+ if (deletePosition < existing.length - 1)
+ // copy elements succeeding the one to be removed
+ System.arraycopy(existing, deletePosition + 1, newValue, deletePosition, newValue.length - deletePosition);
+ return newValue;
+ }
+
+ static String[][] insert(String[][] existing, QualifiedName propertyName, String propertyValue) {
+ // look for the right spot where to insert the new guy
+ int index = search(existing, propertyName);
+ if (index >= 0) {
+ // found existing occurrence - just replace the value
+ existing[index][2] = propertyValue;
+ return existing;
+ }
+ // not found - insert
+ int insertPosition = -index - 1;
+ String[][] newValue = new String[existing.length + 1][];
+ if (insertPosition > 0)
+ System.arraycopy(existing, 0, newValue, 0, insertPosition);
+ newValue[insertPosition] = new String[] {propertyName.getQualifier(), propertyName.getLocalName(), propertyValue};
+ if (insertPosition < existing.length)
+ System.arraycopy(existing, insertPosition, newValue, insertPosition + 1, existing.length - insertPosition);
+ return newValue;
+ }
+
+ /**
+ * Merges two entries (are always sorted). Duplicated additions replace existing ones.
+ */
+ static Object merge(String[][] base, String[][] additions) {
+ int additionPointer = 0;
+ int basePointer = 0;
+ int added = 0;
+ String[][] result = new String[base.length + additions.length][];
+ while (basePointer < base.length && additionPointer < additions.length) {
+ int comparison = COMPARATOR.compare(base[basePointer], additions[additionPointer]);
+ if (comparison == 0) {
+ result[added++] = additions[additionPointer++];
+ // duplicate, override
+ basePointer++;
+ } else if (comparison < 0)
+ result[added++] = base[basePointer++];
+ else
+ result[added++] = additions[additionPointer++];
+ }
+ // copy the remaining states from either additions or base arrays
+ String[][] remaining = basePointer == base.length ? additions : base;
+ int remainingPointer = basePointer == base.length ? additionPointer : basePointer;
+ int remainingCount = remaining.length - remainingPointer;
+ System.arraycopy(remaining, remainingPointer, result, added, remainingCount);
+ added += remainingCount;
+ if (added == base.length + additions.length)
+ // no collisions
+ return result;
+ // there were collisions, need to compact
+ String[][] finalResult = new String[added][];
+ System.arraycopy(result, 0, finalResult, 0, finalResult.length);
+ return finalResult;
+ }
+
+ private static int search(String[][] existing, QualifiedName propertyName) {
+ return Arrays.binarySearch(existing, new String[] {propertyName.getQualifier(), propertyName.getLocalName(), null}, COMPARATOR);
+ }
+
+ public PropertyEntry(IPath path, PropertyEntry base) {
+ super(path);
+ //copy 2-dimensional array [x][y]
+ int xLen = base.value.length;
+ this.value = new String[xLen][];
+ for (int i = 0; i < xLen; i++) {
+ int yLen = base.value[i].length;
+ this.value[i] = new String[yLen];
+ System.arraycopy(base.value[i], 0, value[i], 0, yLen);
+ }
+ }
+
+ /**
+ * @param path
+ * @param value is a String[][] {{propertyKey, propertyValue}}
+ */
+ protected PropertyEntry(IPath path, String[][] value) {
+ super(path);
+ this.value = value;
+ }
+
+ /**
+ * Compacts the data array removing any null slots. If non-null slots
+ * are found, the entry is marked for removal.
+ */
+ private void compact() {
+ if (!isDirty())
+ return;
+ int occurrences = 0;
+ for (int i = 0; i < value.length; i++)
+ if (value[i] != null)
+ value[occurrences++] = value[i];
+ if (occurrences == value.length)
+ // no states deleted
+ return;
+ if (occurrences == 0) {
+ // no states remaining
+ value = EMPTY_DATA;
+ delete();
+ return;
+ }
+ String[][] result = new String[occurrences][];
+ System.arraycopy(value, 0, result, 0, occurrences);
+ value = result;
+ }
+
+ @Override
+ public int getOccurrences() {
+ return value == null ? 0 : value.length;
+ }
+
+ public String getProperty(QualifiedName name) {
+ int index = search(value, name);
+ return index < 0 ? null : value[index][2];
+ }
+
+ public QualifiedName getPropertyName(int i) {
+ return new QualifiedName(this.value[i][0], this.value[i][1]);
+ }
+
+ public String getPropertyValue(int i) {
+ return this.value[i][2];
+ }
+
+ @Override
+ public Object getValue() {
+ return value;
+ }
+
+ @Override
+ public void visited() {
+ compact();
+ }
+ }
+
+ public static final byte INDEX = 1;
+
+ public static final byte QNAME = 2;
+
+ /** Version number for the current implementation file's format.
+ *
+ */
+ private static final byte VERSION = 1;
+
+ private final List qualifierIndex = new ArrayList();
+
+ public PropertyBucket() {
+ super();
+ }
+
+ @Override
+ protected Entry createEntry(IPath path, Object value) {
+ return new PropertyEntry(path, (String[][]) value);
+ }
+
+ private PropertyEntry getEntry(IPath path) {
+ String pathAsString = path.toString();
+ String[][] existing = (String[][]) getEntryValue(pathAsString);
+ if (existing == null)
+ return null;
+ return new PropertyEntry(path, existing);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.internal.localstore.Bucket#getIndexFileName()
+ */
+ @Override
+ protected String getIndexFileName() {
+ return "properties.index"; //$NON-NLS-1$
+ }
+
+ public String getProperty(IPath path, QualifiedName name) {
+ PropertyEntry entry = getEntry(path);
+ if (entry == null)
+ return null;
+ return entry.getProperty(name);
+ }
+
+ @Override
+ protected byte getVersion() {
+ return VERSION;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.internal.localstore.Bucket#getVersionFileName()
+ */
+ @Override
+ protected String getVersionFileName() {
+ return "properties.version"; //$NON-NLS-1$
+ }
+
+ @Override
+ public void load(String newProjectName, File baseLocation, boolean force) throws CoreException {
+ qualifierIndex.clear();
+ super.load(newProjectName, baseLocation, force);
+ }
+
+ @Override
+ protected Object readEntryValue(DataInputStream source) throws IOException, CoreException {
+ int length = source.readUnsignedShort();
+ String[][] properties = new String[length][3];
+ for (int j = 0; j < properties.length; j++) {
+ // qualifier
+ byte constant = source.readByte();
+ switch (constant) {
+ case QNAME :
+ properties[j][0] = source.readUTF();
+ qualifierIndex.add(properties[j][0]);
+ break;
+ case INDEX :
+ properties[j][0] = qualifierIndex.get(source.readInt());
+ break;
+ default :
+ //if we get here the properties file is corrupt
+ IPath resourcePath = projectName == null ? Path.ROOT : Path.ROOT.append(projectName);
+ String msg = NLS.bind(Messages.properties_readProperties, resourcePath.toString());
+ throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, null, msg, null);
+ }
+ // localName
+ properties[j][1] = source.readUTF();
+ // propertyValue
+ properties[j][2] = source.readUTF();
+ }
+ return properties;
+ }
+
+ @Override
+ public void save() throws CoreException {
+ qualifierIndex.clear();
+ super.save();
+ }
+
+ public void setProperties(PropertyEntry entry) {
+ IPath path = entry.getPath();
+ String[][] additions = (String[][]) entry.getValue();
+ String pathAsString = path.toString();
+ String[][] existing = (String[][]) getEntryValue(pathAsString);
+ if (existing == null) {
+ setEntryValue(pathAsString, additions);
+ return;
+ }
+ setEntryValue(pathAsString, PropertyEntry.merge(existing, additions));
+ }
+
+ public void setProperty(IPath path, QualifiedName name, String value) {
+ String pathAsString = path.toString();
+ String[][] existing = (String[][]) getEntryValue(pathAsString);
+ if (existing == null) {
+ if (value != null)
+ setEntryValue(pathAsString, new String[][] {{name.getQualifier(), name.getLocalName(), value}});
+ return;
+ }
+ String[][] newValue;
+ if (value != null)
+ newValue = PropertyEntry.insert(existing, name, value);
+ else
+ newValue = PropertyEntry.delete(existing, name);
+ // even if newValue == existing we should mark as dirty (insert may just change the existing array)
+ setEntryValue(pathAsString, newValue);
+ }
+
+ @Override
+ protected void writeEntryValue(DataOutputStream destination, Object entryValue) throws IOException {
+ String[][] properties = (String[][]) entryValue;
+ destination.writeShort(properties.length);
+ for (int j = 0; j < properties.length; j++) {
+ // writes the property key qualifier
+ int index = qualifierIndex.indexOf(properties[j][0]);
+ if (index == -1) {
+ destination.writeByte(QNAME);
+ destination.writeUTF(properties[j][0]);
+ qualifierIndex.add(properties[j][0]);
+ } else {
+ destination.writeByte(INDEX);
+ destination.writeInt(index);
+ }
+ // then the local name
+ destination.writeUTF(properties[j][1]);
+ // then the property value
+ destination.writeUTF(properties[j][2]);
+ }
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/properties/PropertyManager2.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/properties/PropertyManager2.java
new file mode 100644
index 0000000000..161ac5d39b
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/properties/PropertyManager2.java
@@ -0,0 +1,190 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2016 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.properties;
+
+import java.io.File;
+import java.util.*;
+import org.eclipse.core.internal.localstore.Bucket;
+import org.eclipse.core.internal.localstore.Bucket.Entry;
+import org.eclipse.core.internal.localstore.BucketTree;
+import org.eclipse.core.internal.properties.PropertyBucket.PropertyEntry;
+import org.eclipse.core.internal.resources.*;
+import org.eclipse.core.internal.utils.Messages;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IResourceStatus;
+import org.eclipse.core.runtime.*;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * @see org.eclipse.core.internal.properties.IPropertyManager
+ */
+public class PropertyManager2 implements IPropertyManager {
+ private static final int MAX_VALUE_SIZE = 2 * 1024;
+
+ class PropertyCopyVisitor extends Bucket.Visitor {
+ private List changes = new ArrayList();
+ private IPath destination;
+ private IPath source;
+
+ public PropertyCopyVisitor(IPath source, IPath destination) {
+ this.source = source;
+ this.destination = destination;
+ }
+
+ @Override
+ public void afterSaving(Bucket bucket) throws CoreException {
+ saveChanges((PropertyBucket) bucket);
+ changes.clear();
+ }
+
+ private void saveChanges(PropertyBucket bucket) throws CoreException {
+ if (changes.isEmpty())
+ return;
+ // make effective all changes collected
+ Iterator i = changes.iterator();
+ PropertyEntry entry = i.next();
+ tree.loadBucketFor(entry.getPath());
+ bucket.setProperties(entry);
+ while (i.hasNext())
+ bucket.setProperties(i.next());
+ bucket.save();
+ }
+
+ @Override
+ public int visit(Entry entry) {
+ PropertyEntry sourceEntry = (PropertyEntry) entry;
+ IPath destinationPath = destination.append(sourceEntry.getPath().removeFirstSegments(source.segmentCount()));
+ PropertyEntry destinationEntry = new PropertyEntry(destinationPath, sourceEntry);
+ changes.add(destinationEntry);
+ return CONTINUE;
+ }
+ }
+
+ BucketTree tree;
+
+ public PropertyManager2(Workspace workspace) {
+ this.tree = new BucketTree(workspace, new PropertyBucket());
+ }
+
+ @Override
+ public void closePropertyStore(IResource target) throws CoreException {
+ // ensure any uncommitted are written to disk
+ tree.getCurrent().save();
+ // flush in-memory state to avoid confusion if another project is later
+ // created with the same name
+ tree.getCurrent().flush();
+ }
+
+ @Override
+ public synchronized void copy(IResource source, IResource destination, int depth) throws CoreException {
+ copyProperties(source.getFullPath(), destination.getFullPath(), depth);
+ }
+
+ /**
+ * Copies all properties from the source path to the target path, to the given depth.
+ */
+ private void copyProperties(final IPath source, final IPath destination, int depth) throws CoreException {
+ Assert.isLegal(source.segmentCount() > 0);
+ Assert.isLegal(destination.segmentCount() > 0);
+ Assert.isLegal(source.segmentCount() > 1 || destination.segmentCount() == 1);
+
+ // copy history by visiting the source tree
+ PropertyCopyVisitor copyVisitor = new PropertyCopyVisitor(source, destination);
+ tree.accept(copyVisitor, source, BucketTree.DEPTH_INFINITE);
+ }
+
+ @Override
+ public synchronized void deleteProperties(IResource target, int depth) throws CoreException {
+ tree.accept(new PropertyBucket.Visitor() {
+ @Override
+ public int visit(Entry entry) {
+ entry.delete();
+ return CONTINUE;
+ }
+ }, target.getFullPath(), depth == IResource.DEPTH_INFINITE ? BucketTree.DEPTH_INFINITE : depth);
+ }
+
+ @Override
+ public void deleteResource(IResource target) throws CoreException {
+ deleteProperties(target, IResource.DEPTH_INFINITE);
+ }
+
+ @Override
+ public synchronized Map getProperties(IResource target) throws CoreException {
+ final Map result = new HashMap();
+ tree.accept(new PropertyBucket.Visitor() {
+ @Override
+ public int visit(Entry entry) {
+ PropertyEntry propertyEntry = (PropertyEntry) entry;
+ int propertyCount = propertyEntry.getOccurrences();
+ for (int i = 0; i < propertyCount; i++)
+ result.put(propertyEntry.getPropertyName(i), propertyEntry.getPropertyValue(i));
+ return CONTINUE;
+ }
+ }, target.getFullPath(), BucketTree.DEPTH_ZERO);
+ return result;
+ }
+
+ @Override
+ public synchronized String getProperty(IResource target, QualifiedName name) throws CoreException {
+ if (name.getQualifier() == null) {
+ String message = Messages.properties_qualifierIsNull;
+ throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, target.getFullPath(), message, null);
+ }
+ IPath resourcePath = target.getFullPath();
+ PropertyBucket current = (PropertyBucket) tree.getCurrent();
+ tree.loadBucketFor(resourcePath);
+ return current.getProperty(resourcePath, name);
+ }
+
+ public BucketTree getTree() {
+ return tree;
+ }
+
+ public File getVersionFile() {
+ return tree.getVersionFile();
+ }
+
+ @Override
+ public synchronized void setProperty(IResource target, QualifiedName name, String value) throws CoreException {
+ //resource may have been deleted concurrently
+ //must check for existence within synchronized method
+ Resource resource = (Resource) target;
+ ResourceInfo info = resource.getResourceInfo(false, false);
+ int flags = resource.getFlags(info);
+ resource.checkAccessible(flags);
+ // enforce the limit stated by the spec
+ if (value != null && value.length() > MAX_VALUE_SIZE) {
+ String message = NLS.bind(Messages.properties_valueTooLong, new Object[] {name.getQualifier(), name.getLocalName(), new Integer(MAX_VALUE_SIZE).toString()});
+ throw new ResourceException(IResourceStatus.FAILED_WRITE_METADATA, target.getFullPath(), message, null);
+ }
+ if (name.getQualifier() == null) {
+ String message = Messages.properties_qualifierIsNull;
+ throw new ResourceException(IResourceStatus.FAILED_WRITE_METADATA, target.getFullPath(), message, null);
+ }
+
+ IPath resourcePath = target.getFullPath();
+ tree.loadBucketFor(resourcePath);
+ PropertyBucket current = (PropertyBucket) tree.getCurrent();
+ current.setProperty(resourcePath, name, value);
+ current.save();
+ }
+
+ @Override
+ public void shutdown(IProgressMonitor monitor) throws CoreException {
+ tree.close();
+ }
+
+ @Override
+ public void startup(IProgressMonitor monitor) {
+ // nothing to do
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/FilePropertyTester.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/FilePropertyTester.java
new file mode 100644
index 0000000000..d9e67bc258
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/FilePropertyTester.java
@@ -0,0 +1,123 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2014 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.core.internal.propertytester;
+
+import org.eclipse.core.internal.utils.Policy;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.runtime.*;
+import org.eclipse.core.runtime.content.*;
+
+/**
+ * A property tester for various properties of files.
+ *
+ * @since 3.2
+ */
+public class FilePropertyTester extends ResourcePropertyTester {
+
+ /**
+ * A property indicating a content type on the selected file (value "contentTypeId").
+ * "kindOf" indicates that the file content type should be the kind of the one given as the expected value.
+ * If "kindOf" is not specified, the file content type identifier should equals the expected value.
+ * @see IContentType#isKindOf(IContentType)
+ */
+ private static final String CONTENT_TYPE_ID = "contentTypeId"; //$NON-NLS-1$
+
+ /**
+ * An argument for "contentTypeId".
+ * "kindOf" indicates that the file content type should be the kind of the one given as the expected value.
+ * If "kindOf" is not specified, the file content type identifier should equals the expected value.
+ * @see IContentType#isKindOf(IContentType)
+ */
+ private static final String IS_KIND_OF = "kindOf"; //$NON-NLS-1$
+
+ /**
+ * An argument for "contentTypeId".
+ * Setting "useFilenameOnly" indicates that the file content type should be determined by the file name only.
+ * If "useFilenameOnly" is not specified, the file content type is determined by both, the file name and content.
+ * @see IContentTypeMatcher#findContentTypeFor(String)
+ */
+ private static final String USE_FILENAME_ONLY = "useFilenameOnly"; //$NON-NLS-1$
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.core.internal.resources.ResourcePropertyTester#test(java.lang.Object,
+ * java.lang.String, java.lang.Object[], java.lang.Object)
+ */
+ @Override
+ public boolean test(Object receiver, String method, Object[] args, Object expectedValue) {
+ if ((receiver instanceof IFile) && method.equals(CONTENT_TYPE_ID))
+ return testContentType((IFile) receiver, toString(expectedValue), isArgumentUsed(args, IS_KIND_OF), isArgumentUsed(args, USE_FILENAME_ONLY));
+ return false;
+ }
+
+ private boolean isArgumentUsed(Object[] args, String value) {
+ for (int i = 0; i < args.length; i++)
+ if (value.equals(args[i]))
+ return true;
+ return false;
+ }
+
+ /**
+ *
+ * Tests whether the content type for file matches
+ * or is a kind of contentTypeId.
+ *
+ *
+ * It is possible that this method call could
+ * cause the file to be read. It is also possible (through poor plug-in
+ * design) for this method to load plug-ins.
+ *
+ *
+ * @param file
+ * The file to test. Must not be null.
+ * @param contentTypeId
+ * The content type to test. Must not be null.
+ * @param isKindOfUsed
+ * Indicates whether the file content type should match contentTypeId
+ * or should be a kind of contentTypeId.
+ * @param useFilenameOnly
+ * Indicates to determine the file content type based on the file name only.
+ * @return true, if the best matching content type for file
+ *
+ *
has an identifier that matches contentTypeId
+ * and isKindOfUsed is false, or
+ *
is a kind of contentTypeId
+ * and isKindOfUsed is true.
+ *
+ * Otherwise it returns false.
+ */
+ private boolean testContentType(final IFile file, String contentTypeId, boolean isKindOfUsed, boolean useFilenameOnly) {
+ final String expectedValue = contentTypeId.trim();
+ IContentType actualContentType = null;
+ if (!useFilenameOnly) {
+ if (!file.exists())
+ return false;
+ IContentDescription contentDescription = null;
+ try {
+ contentDescription = file.getContentDescription();
+ } catch (CoreException e) {
+ Policy.log(IStatus.ERROR, "Core exception while retrieving the content description", e);//$NON-NLS-1$
+ }
+ if (contentDescription != null)
+ actualContentType = contentDescription.getContentType();
+ } else {
+ actualContentType = Platform.getContentTypeManager().findContentTypeFor(file.getName());
+ }
+ if (actualContentType != null) {
+ if (isKindOfUsed)
+ return actualContentType.isKindOf(Platform.getContentTypeManager().getContentType(expectedValue));
+ return expectedValue.equals(actualContentType.getId());
+ }
+ return false;
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/ProjectPropertyTester.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/ProjectPropertyTester.java
new file mode 100644
index 0000000000..4f981d9cd5
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/ProjectPropertyTester.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2014 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.core.internal.propertytester;
+
+import org.eclipse.core.resources.IProject;
+
+/**
+ * A property tester for various properties of projects.
+ *
+ * @since 3.2
+ */
+public class ProjectPropertyTester extends ResourcePropertyTester {
+
+ /**
+ * A property indicating whether the project is open (value "open").
+ */
+ private static final String OPEN = "open"; //$NON-NLS-1$
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.core.internal.resources.ResourcePropertyTester#test(java.lang.Object,
+ * java.lang.String, java.lang.Object[], java.lang.Object)
+ */
+ @Override
+ public boolean test(Object receiver, String method, Object[] args, Object expectedValue) {
+ if ((receiver instanceof IProject) && method.equals(OPEN))
+ return ((IProject) receiver).isOpen() == toBoolean(expectedValue);
+ return false;
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/ResourceMappingPropertyTester.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/ResourceMappingPropertyTester.java
new file mode 100644
index 0000000000..a4771beaff
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/ResourceMappingPropertyTester.java
@@ -0,0 +1,70 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2014 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.propertytester;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.mapping.ResourceMapping;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.QualifiedName;
+
+/**
+ * A property tester for various properties of resource mappings
+ *
+ * @since 3.2
+ */
+public class ResourceMappingPropertyTester extends ResourcePropertyTester {
+ @Override
+ public boolean test(Object receiver, String method, Object[] args, Object expectedValue) {
+ if (!(receiver instanceof ResourceMapping))
+ return false;
+ if (!method.equals(PROJECT_PERSISTENT_PROPERTY))
+ return false;
+ //Note: we currently say the test is satisfied if any project associated
+ //with the mapping satisfies the test.
+ IProject[] projects = ((ResourceMapping) receiver).getProjects();
+ if (projects.length == 0)
+ return false;
+ String propertyName;
+ String expectedVal;
+ if (args.length == 0) {
+ propertyName = toString(expectedValue);
+ expectedVal = null;//any value will do
+ } else if (args.length == 1) {
+ propertyName = toString(args[0]);
+ expectedVal = null;//any value will do
+ } else {
+ propertyName = toString(args[0]);
+ expectedVal = toString(args[1]);
+ }
+ QualifiedName key = toQualifedName(propertyName);
+ boolean found = false;
+ for (int i = 0; i < projects.length; i++) {
+ try {
+ Object actualVal = projects[i].getPersistentProperty(key);
+ //the value is not set, so keep looking on other projects
+ if (actualVal == null)
+ continue;
+ //record that we have found at least one value
+ found = true;
+ //expected value of null means we expect *any* value, rather than expecting no value
+ if (expectedVal == null)
+ continue;
+ //if the value we find does not match, then the property is not satisfied
+ if (!expectedVal.equals(actualVal.toString()))
+ return false;
+ } catch (CoreException e) {
+ // ignore
+ }
+ }
+ //if any projects had the property set, the condition is satisfied
+ return found;
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/ResourcePropertyTester.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/ResourcePropertyTester.java
new file mode 100644
index 0000000000..e0d0739856
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/ResourcePropertyTester.java
@@ -0,0 +1,221 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2015 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.propertytester;
+
+import org.eclipse.core.expressions.PropertyTester;
+import org.eclipse.core.resources.*;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.QualifiedName;
+
+/**
+ * A property tester for various properties of resources.
+ *
+ * @since 3.2
+ */
+public class ResourcePropertyTester extends PropertyTester {
+ /**
+ * A property indicating the file extension (value "extension").
+ * "*" and "?" wild cards are supported.
+ */
+ protected static final String EXTENSION = "extension"; //$NON-NLS-1$
+
+ /**
+ * A property indicating the file name (value "name"). "*"
+ * and "?" wild cards are supported.
+ */
+ protected static final String NAME = "name"; //$NON-NLS-1$
+
+ /**
+ * A property indicating the file path (value "path"). "*"
+ * and "?" wild cards are supported.
+ */
+ protected static final String PATH = "path"; //$NON-NLS-1$
+
+ /**
+ * A property indicating a persistent property on the selected resource
+ * (value "persistentProperty"). If two arguments are given,
+ * this treats the first as the property name, and the second as the expected
+ * property value. If only one argument (or just the expected value) is
+ * given, this treats it as the property name, and simply tests for existence of
+ * the property on the resource.
+ */
+ protected static final String PERSISTENT_PROPERTY = "persistentProperty"; //$NON-NLS-1$
+
+ /**
+ * A property indicating the project nature (value
+ * "projectNature").
+ */
+ protected static final String PROJECT_NATURE = "projectNature"; //$NON-NLS-1$
+
+ /**
+ * A property indicating a persistent property on the selected resource's
+ * project. (value "projectPersistentProperty"). If two
+ * arguments are given, this treats the first as the property name, and the
+ * second as the expected property value. If only one argument (or just the
+ * expected value) is given, this treats it as the property name, and simply
+ * tests for existence of the property on the resource.
+ */
+ protected static final String PROJECT_PERSISTENT_PROPERTY = "projectPersistentProperty"; //$NON-NLS-1$
+
+ /**
+ * A property indicating a session property on the selected resource's
+ * project. (value "projectSessionProperty"). If two
+ * arguments are given, this treats the first as the property name, and the
+ * second as the expected property value. If only one argument (or just the
+ * expected value) is given, this treats it as the property name, and simply
+ * tests for existence of the property on the resource.
+ */
+ protected static final String PROJECT_SESSION_PROPERTY = "projectSessionProperty"; //$NON-NLS-1$
+
+ /**
+ * A property indicating whether the file is read only (value
+ * "readOnly").
+ */
+ protected static final String READ_ONLY = "readOnly"; //$NON-NLS-1$
+
+ /**
+ * A property indicating a session property on the selected resource (value
+ * "sessionProperty"). If two arguments are given, this
+ * treats the first as the property name, and the second as the expected
+ * property value. If only one argument (or just the expected value) is
+ * given, this treats it as the property name, and simply tests for existence of
+ * the property on the resource.
+ */
+ protected static final String SESSION_PROPERTY = "sessionProperty"; //$NON-NLS-1$
+
+ @Override
+ public boolean test(Object receiver, String method, Object[] args, Object expectedValue) {
+ if (!(receiver instanceof IResource))
+ return false;
+ IResource res = (IResource) receiver;
+ if (method.equals(NAME)) {
+ return new StringMatcher(toString(expectedValue)).match(res.getName());
+ } else if (method.equals(PATH)) {
+ return new StringMatcher(toString(expectedValue)).match(res.getFullPath().toString());
+ } else if (method.equals(EXTENSION)) {
+ return new StringMatcher(toString(expectedValue)).match(res.getFileExtension());
+ } else if (method.equals(READ_ONLY)) {
+ ResourceAttributes attr = res.getResourceAttributes();
+ return (attr != null && attr.isReadOnly()) == toBoolean(expectedValue);
+ } else if (method.equals(PROJECT_NATURE)) {
+ try {
+ IProject proj = res.getProject();
+ return proj != null && proj.isAccessible() && proj.hasNature(toString(expectedValue));
+ } catch (CoreException e) {
+ return false;
+ }
+ } else if (method.equals(PERSISTENT_PROPERTY)) {
+ return testProperty(res, true, args, expectedValue);
+ } else if (method.equals(PROJECT_PERSISTENT_PROPERTY)) {
+ return testProperty(res.getProject(), true, args, expectedValue);
+ } else if (method.equals(SESSION_PROPERTY)) {
+ return testProperty(res, false, args, expectedValue);
+ } else if (method.equals(PROJECT_SESSION_PROPERTY)) {
+ return testProperty(res.getProject(), false, args, expectedValue);
+ }
+ return false;
+ }
+
+ /**
+ * Tests whether a session or persistent property on the resource or its
+ * project matches the given value.
+ *
+ * @param resource
+ * the resource to check
+ * @param persistentFlag
+ * true for a persistent property,
+ * false for a session property
+ * @param args
+ * additional arguments to evaluate the property.
+ * If of length 0, this treats the expectedValue as the property name
+ * and does a simple check for existence of the property.
+ * If of length 1, this treats the first argument as the property name
+ * and does a simple check for existence of the property.
+ * If of length 2, this treats the first argument as the property name,
+ * the second argument as the expected value, and checks for equality
+ * with the actual property value.
+ * @param expectedValue
+ * used only if args is of length 0 (see Javadoc for args parameter)
+ * @return whether there is a match
+ */
+ protected boolean testProperty(IResource resource, boolean persistentFlag, Object[] args, Object expectedValue) {
+ //the project of IWorkspaceRoot is null
+ if (resource == null)
+ return false;
+ String propertyName;
+ String expectedVal;
+ if (args.length == 0) {
+ propertyName = toString(expectedValue);
+ expectedVal = null;
+ } else if (args.length == 1) {
+ propertyName = toString(args[0]);
+ expectedVal = null;
+ } else {
+ propertyName = toString(args[0]);
+ expectedVal = toString(args[1]);
+ }
+ try {
+ QualifiedName key = toQualifedName(propertyName);
+ Object actualVal = persistentFlag ? resource.getPersistentProperty(key) : resource.getSessionProperty(key);
+ if (actualVal == null)
+ return false;
+ return expectedVal == null || expectedVal.equals(actualVal.toString());
+ } catch (CoreException e) {
+ //if the resource is not accessible, fall through and return false below
+ }
+ return false;
+ }
+
+ /**
+ * Converts the given expected value to a boolean.
+ *
+ * @param expectedValue
+ * the expected value (may be null).
+ * @return false if the expected value equals Boolean.FALSE,
+ * true otherwise
+ */
+ protected boolean toBoolean(Object expectedValue) {
+ if (expectedValue instanceof Boolean) {
+ return ((Boolean) expectedValue).booleanValue();
+ }
+ return true;
+ }
+
+ /**
+ * Converts the given name to a qualified name.
+ *
+ * @param name the name
+ * @return the qualified name
+ */
+ protected QualifiedName toQualifedName(String name) {
+ QualifiedName key;
+ int dot = name.lastIndexOf('.');
+ if (dot != -1) {
+ key = new QualifiedName(name.substring(0, dot), name.substring(dot + 1));
+ } else {
+ key = new QualifiedName(null, name);
+ }
+ return key;
+ }
+
+ /**
+ * Converts the given expected value to a String.
+ *
+ * @param expectedValue
+ * the expected value (may be null).
+ * @return the empty string if the expected value is null,
+ * otherwise the toString() representation of the
+ * expected value
+ */
+ protected String toString(Object expectedValue) {
+ return expectedValue == null ? "" : expectedValue.toString(); //$NON-NLS-1$
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/StringMatcher.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/StringMatcher.java
new file mode 100644
index 0000000000..98cf416699
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/StringMatcher.java
@@ -0,0 +1,230 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.propertytester;
+
+import java.util.ArrayList;
+
+/**
+ * A string pattern matcher, supporting "*" and "?" wild cards.
+ *
+ * @since 3.2
+ */
+public class StringMatcher {
+ private static final char SINGLE_WILD_CARD = '\u0000';
+
+ /**
+ * Boundary value beyond which we don't need to search in the text
+ */
+ private int bound = 0;
+
+ private boolean hasLeadingStar;
+
+ private boolean hasTrailingStar;
+
+ private final String pattern;
+
+ private final int patternLength;
+
+ /**
+ * The pattern split into segments separated by *
+ */
+ private String segments[];
+
+ /**
+ * StringMatcher constructor takes in a String object that is a simple
+ * pattern which may contain '*' for 0 and many characters and
+ * '?' for exactly one character.
+ *
+ * Literal '*' and '?' characters must be escaped in the pattern
+ * e.g., "\*" means literal "*", etc.
+ *
+ * Escaping any other character (including the escape character itself),
+ * just results in that character in the pattern.
+ * e.g., "\a" means "a" and "\\" means "\"
+ *
+ * If invoking the StringMatcher with string literals in Java, don't forget
+ * escape characters are represented by "\\".
+ *
+ * @param pattern the pattern to match text against
+ */
+ public StringMatcher(String pattern) {
+ if (pattern == null)
+ throw new IllegalArgumentException();
+ this.pattern = pattern;
+ patternLength = pattern.length();
+ parseWildCards();
+ }
+
+ /**
+ * @param text a simple regular expression that may only contain '?'(s)
+ * @param start the starting index in the text for search, inclusive
+ * @param end the stopping point of search, exclusive
+ * @param p a simple regular expression that may contain '?'
+ * @return the starting index in the text of the pattern , or -1 if not found
+ */
+ private int findPosition(String text, int start, int end, String p) {
+ boolean hasWildCard = p.indexOf(SINGLE_WILD_CARD) >= 0;
+ int plen = p.length();
+ for (int i = start, max = end - plen; i <= max; ++i) {
+ if (hasWildCard) {
+ if (regExpRegionMatches(text, i, p, 0, plen))
+ return i;
+ } else {
+ if (text.regionMatches(true, i, p, 0, plen))
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Given the starting (inclusive) and the ending (exclusive) positions in the
+ * text, determine if the given substring matches with aPattern
+ * @return true if the specified portion of the text matches the pattern
+ * @param text a String object that contains the substring to match
+ */
+ public boolean match(String text) {
+ if (text == null)
+ return false;
+ final int end = text.length();
+ final int segmentCount = segments.length;
+ if (segmentCount == 0 && (hasLeadingStar || hasTrailingStar)) // pattern contains only '*'(s)
+ return true;
+ if (end == 0)
+ return patternLength == 0;
+ if (patternLength == 0)
+ return false;
+ int currentTextPosition = 0;
+ if ((end - bound) < 0)
+ return false;
+ int segmentIndex = 0;
+ String current = segments[segmentIndex];
+
+ /* process first segment */
+ if (!hasLeadingStar) {
+ int currentLength = current.length();
+ if (!regExpRegionMatches(text, 0, current, 0, currentLength))
+ return false;
+ segmentIndex++;
+ currentTextPosition = currentTextPosition + currentLength;
+ }
+ if ((segmentCount == 1) && (!hasLeadingStar) && (!hasTrailingStar)) {
+ // only one segment to match, no wild cards specified
+ return currentTextPosition == end;
+ }
+ /* process middle segments */
+ while (segmentIndex < segmentCount) {
+ current = segments[segmentIndex];
+ int currentMatch = findPosition(text, currentTextPosition, end, current);
+ if (currentMatch < 0)
+ return false;
+ currentTextPosition = currentMatch + current.length();
+ segmentIndex++;
+ }
+
+ /* process final segment */
+ if (!hasTrailingStar && currentTextPosition != end) {
+ int currentLength = current.length();
+ return regExpRegionMatches(text, end - currentLength, current, 0, currentLength);
+ }
+ return segmentIndex == segmentCount;
+ }
+
+ /**
+ * Parses the pattern into segments separated by wildcard '*' characters.
+ */
+ private void parseWildCards() {
+ if (pattern.startsWith("*"))//$NON-NLS-1$
+ hasLeadingStar = true;
+ if (pattern.endsWith("*")) {//$NON-NLS-1$
+ /* make sure it's not an escaped wildcard */
+ if (patternLength > 1 && pattern.charAt(patternLength - 2) != '\\') {
+ hasTrailingStar = true;
+ }
+ }
+
+ ArrayList temp = new ArrayList();
+
+ int pos = 0;
+ StringBuffer buf = new StringBuffer();
+ while (pos < patternLength) {
+ char c = pattern.charAt(pos++);
+ switch (c) {
+ case '\\' :
+ if (pos >= patternLength) {
+ buf.append(c);
+ } else {
+ char next = pattern.charAt(pos++);
+ /* if it's an escape sequence */
+ if (next == '*' || next == '?' || next == '\\') {
+ buf.append(next);
+ } else {
+ /* not an escape sequence, just insert literally */
+ buf.append(c);
+ buf.append(next);
+ }
+ }
+ break;
+ case '*' :
+ if (buf.length() > 0) {
+ /* new segment */
+ temp.add(buf.toString());
+ bound += buf.length();
+ buf.setLength(0);
+ }
+ break;
+ case '?' :
+ /* append special character representing single match wildcard */
+ buf.append(SINGLE_WILD_CARD);
+ break;
+ default :
+ buf.append(c);
+ }
+ }
+
+ /* add last buffer to segment list */
+ if (buf.length() > 0) {
+ temp.add(buf.toString());
+ bound += buf.length();
+ }
+ segments = temp.toArray(new String[temp.size()]);
+ }
+
+ /**
+ *
+ * @return boolean
+ * @param text a String to match
+ * @param tStart the starting index of match, inclusive
+ * @param p a simple regular expression that may contain '?'
+ * @param pStart The start position in the pattern
+ * @param plen The length of the pattern
+ */
+ private boolean regExpRegionMatches(String text, int tStart, String p, int pStart, int plen) {
+ while (plen-- > 0) {
+ char tchar = text.charAt(tStart++);
+ char pchar = p.charAt(pStart++);
+
+ // process wild cards, skipping single wild cards
+ if (pchar == SINGLE_WILD_CARD)
+ continue;
+ if (pchar == tchar)
+ continue;
+ if (Character.toUpperCase(tchar) == Character.toUpperCase(pchar))
+ continue;
+ // comparing after converting to upper case doesn't handle all cases;
+ // also compare after converting to lower case
+ if (Character.toLowerCase(tchar) == Character.toLowerCase(pchar))
+ continue;
+ return false;
+ }
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/InternalRefreshProvider.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/InternalRefreshProvider.java
new file mode 100644
index 0000000000..26e0f9bae1
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/InternalRefreshProvider.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2005 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM - Initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.refresh;
+
+import org.eclipse.core.internal.resources.Workspace;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.refresh.IRefreshMonitor;
+
+/**
+ * Internal abstract superclass of all refresh providers. This class must not be
+ * subclassed directly by clients. All refresh providers must subclass the public
+ * API class org.eclipse.core.resources.refresh.RefreshProvider.
+ *
+ * @since 3.0
+ */
+public class InternalRefreshProvider {
+ /* (non-Javadoc)
+ * @see org.eclipse.core.resources.refresh.RefreshProvider#createPollingMonitor(IResource)
+ */
+ protected IRefreshMonitor createPollingMonitor(IResource resource) {
+ PollingMonitor monitor = ((Workspace)resource.getWorkspace()).getRefreshManager().monitors.pollMonitor;
+ monitor.monitor(resource);
+ return monitor;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.resources.refresh.RefreshProvider#resetMonitors(IResource)
+ */
+ public void resetMonitors(IResource resource) {
+ MonitorManager manager = ((Workspace)resource.getWorkspace()).getRefreshManager().monitors;
+ manager.unmonitor(resource);
+ manager.monitor(resource);
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/MonitorManager.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/MonitorManager.java
new file mode 100644
index 0000000000..f92ecc601b
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/MonitorManager.java
@@ -0,0 +1,355 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2015 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM - Initial API and implementation
+ * James Blackburn (Broadcom Corp.) - ongoing development
+ *******************************************************************************/
+package org.eclipse.core.internal.refresh;
+
+import java.util.*;
+import java.util.Map.Entry;
+import org.eclipse.core.internal.events.ILifecycleListener;
+import org.eclipse.core.internal.events.LifecycleEvent;
+import org.eclipse.core.internal.resources.Workspace;
+import org.eclipse.core.internal.utils.Messages;
+import org.eclipse.core.internal.utils.Policy;
+import org.eclipse.core.resources.*;
+import org.eclipse.core.resources.refresh.IRefreshMonitor;
+import org.eclipse.core.resources.refresh.RefreshProvider;
+import org.eclipse.core.runtime.*;
+
+/**
+ * Manages monitors by creating new monitors when projects are added and
+ * removing monitors when projects are removed. Also handles the polling
+ * mechanism when contributed native monitors cannot handle a project.
+ *
+ * @since 3.0
+ */
+class MonitorManager implements ILifecycleListener, IPathVariableChangeListener, IResourceChangeListener, IResourceDeltaVisitor {
+ /**
+ * The PollingMonitor in charge of doing file-system polls.
+ */
+ protected final PollingMonitor pollMonitor;
+ /**
+ * The list of registered monitor factories.
+ */
+ private RefreshProvider[] providers;
+ /**
+ * Reference to the refresh manager.
+ */
+ protected final RefreshManager refreshManager;
+ /**
+ * A mapping of monitors to a list of resources each monitor is responsible for.
+ */
+ protected final Map> registeredMonitors;
+ /**
+ * Reference to the workspace.
+ */
+ protected IWorkspace workspace;
+
+ public MonitorManager(IWorkspace workspace, RefreshManager refreshManager) {
+ this.workspace = workspace;
+ this.refreshManager = refreshManager;
+ registeredMonitors = Collections.synchronizedMap(new HashMap>(10));
+ pollMonitor = new PollingMonitor(refreshManager);
+ }
+
+ /**
+ * Queries extensions of the refreshProviders extension point, and
+ * creates the provider classes. Will never return null.
+ *
+ * @return RefreshProvider[] The array of registered RefreshProvider
+ * objects or an empty array.
+ */
+ private RefreshProvider[] getRefreshProviders() {
+ if (providers != null)
+ return providers;
+ IExtensionPoint extensionPoint = Platform.getExtensionRegistry().getExtensionPoint(ResourcesPlugin.PI_RESOURCES, ResourcesPlugin.PT_REFRESH_PROVIDERS);
+ IConfigurationElement[] infos = extensionPoint.getConfigurationElements();
+ List providerList = new ArrayList(infos.length);
+ for (int i = 0; i < infos.length; i++) {
+ IConfigurationElement configurationElement = infos[i];
+ RefreshProvider provider = null;
+ try {
+ provider = (RefreshProvider) configurationElement.createExecutableExtension("class"); //$NON-NLS-1$
+ } catch (CoreException e) {
+ Policy.log(IStatus.WARNING, Messages.refresh_installError, e);
+ }
+ if (provider != null)
+ providerList.add(provider);
+ }
+ providers = providerList.toArray(new RefreshProvider[providerList.size()]);
+ return providers;
+ }
+
+ /**
+ * Collects the set of root resources that required monitoring. This
+ * includes projects and all linked resources.
+ */
+ private List getResourcesToMonitor() {
+ final List resourcesToMonitor = new ArrayList(10);
+ IProject[] projects = workspace.getRoot().getProjects(IContainer.INCLUDE_HIDDEN);
+ for (int i = 0; i < projects.length; i++) {
+ if (!projects[i].isAccessible())
+ continue;
+ resourcesToMonitor.add(projects[i]);
+ try {
+ IResource[] members = projects[i].members();
+ for (int j = 0; j < members.length; j++)
+ if (members[j].isLinked())
+ resourcesToMonitor.add(members[j]);
+ } catch (CoreException e) {
+ Policy.log(IStatus.WARNING, Messages.refresh_refreshErr, e);
+ }
+ }
+ return resourcesToMonitor;
+ }
+
+ @Override
+ public void handleEvent(LifecycleEvent event) {
+ switch (event.kind) {
+ case LifecycleEvent.PRE_LINK_DELETE :
+ case LifecycleEvent.PRE_PROJECT_CLOSE :
+ case LifecycleEvent.PRE_PROJECT_DELETE :
+ unmonitor(event.resource);
+ break;
+ }
+ }
+
+ private boolean isMonitoring(IResource resource) {
+ synchronized (registeredMonitors) {
+ for (Iterator i = registeredMonitors.keySet().iterator(); i.hasNext();) {
+ List resources = registeredMonitors.get(i.next());
+ if ((resources != null) && (resources.contains(resource)))
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Installs a monitor on the given resource. Returns true if the polling
+ * monitor was installed, and false if a refresh provider was installed.
+ */
+ boolean monitor(IResource resource) {
+ if (isMonitoring(resource))
+ return false;
+ boolean pollingMonitorNeeded = true;
+ RefreshProvider[] refreshProviders = getRefreshProviders();
+ for (int i = 0; i < refreshProviders.length; i++) {
+ IRefreshMonitor monitor = safeInstallMonitor(refreshProviders[i], resource);
+ if (monitor != null) {
+ registerMonitor(monitor, resource);
+ pollingMonitorNeeded = false;
+ }
+ }
+ if (pollingMonitorNeeded) {
+ pollMonitor.monitor(resource);
+ registerMonitor(pollMonitor, resource);
+ }
+ return pollingMonitorNeeded;
+ }
+
+ /* (non-Javadoc)
+ * @see IRefreshResult#monitorFailed
+ */
+ public void monitorFailed(IRefreshMonitor monitor, IResource resource) {
+ if (Policy.DEBUG_AUTO_REFRESH)
+ Policy.debug(RefreshManager.DEBUG_PREFIX + " monitor (" + monitor + ") failed to monitor resource: " + resource); //$NON-NLS-1$ //$NON-NLS-2$
+ if (registeredMonitors == null || monitor == null)
+ return;
+ if (resource == null) {
+ List resources = registeredMonitors.get(monitor);
+ if (resources == null || resources.isEmpty()) {
+ registeredMonitors.remove(monitor);
+ return;
+ }
+ // synchronized: protect the collection during iteration
+ synchronized (registeredMonitors) {
+ for (Iterator i = resources.iterator(); i.hasNext();) {
+ resource = i.next();
+ pollMonitor.monitor(resource);
+ registerMonitor(pollMonitor, resource);
+ }
+ registeredMonitors.remove(monitor);
+ }
+ } else {
+ removeMonitor(monitor, resource);
+ pollMonitor.monitor(resource);
+ registerMonitor(pollMonitor, resource);
+ }
+ }
+
+ /**
+ * @see org.eclipse.core.resources.IPathVariableChangeListener#pathVariableChanged(org.eclipse.core.resources.IPathVariableChangeEvent)
+ */
+ @Override
+ public void pathVariableChanged(IPathVariableChangeEvent event) {
+ if (registeredMonitors.isEmpty())
+ return;
+ String variableName = event.getVariableName();
+ Set invalidResources = new HashSet();
+ for (Iterator> i = registeredMonitors.values().iterator(); i.hasNext();) {
+ for (Iterator j = i.next().iterator(); j.hasNext();) {
+ IResource resource = j.next();
+ IPath rawLocation = resource.getRawLocation();
+ if (rawLocation != null) {
+ if (rawLocation.segmentCount() > 0 && variableName.equals(rawLocation.segment(0)) && !invalidResources.contains(resource)) {
+ invalidResources.add(resource);
+ }
+ }
+ }
+ }
+ if (!invalidResources.isEmpty()) {
+ for (Iterator i = invalidResources.iterator(); i.hasNext();) {
+ IResource resource = i.next();
+ unmonitor(resource);
+ monitor(resource);
+ }
+ }
+ }
+
+ private void registerMonitor(IRefreshMonitor monitor, IResource resource) {
+ // synchronized: protect the collection during add
+ synchronized (registeredMonitors) {
+ List resources = registeredMonitors.get(monitor);
+ if (resources == null) {
+ resources = new ArrayList(1);
+ registeredMonitors.put(monitor, resources);
+ }
+ if (!resources.contains(resource))
+ resources.add(resource);
+ }
+ if (Policy.DEBUG_AUTO_REFRESH)
+ Policy.debug(RefreshManager.DEBUG_PREFIX + " added monitor (" + monitor + ") on resource: " + resource); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ private void removeMonitor(IRefreshMonitor monitor, IResource resource) {
+ // synchronized: protect the collection during remove
+ synchronized (registeredMonitors) {
+ List resources = registeredMonitors.get(monitor);
+ if (resources != null && !resources.isEmpty())
+ resources.remove(resource);
+ else
+ registeredMonitors.remove(monitor);
+ }
+ if (Policy.DEBUG_AUTO_REFRESH)
+ Policy.debug(RefreshManager.DEBUG_PREFIX + " removing monitor (" + monitor + ") on resource: " + resource); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ private IRefreshMonitor safeInstallMonitor(RefreshProvider provider, IResource resource) {
+ Throwable t = null;
+ try {
+ return provider.installMonitor(resource, refreshManager);
+ } catch (Exception e) {
+ t = e;
+ } catch (LinkageError e) {
+ t = e;
+ }
+ IStatus error = new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, 1, Messages.refresh_installError, t);
+ Policy.log(error);
+ return null;
+ }
+
+ /**
+ * Start the monitoring of resources by all monitors.
+ */
+ public void start() {
+ boolean refreshNeeded = false;
+ for (Iterator i = getResourcesToMonitor().iterator(); i.hasNext();)
+ refreshNeeded |= !monitor(i.next());
+ workspace.getPathVariableManager().addChangeListener(this);
+ workspace.addResourceChangeListener(this, IResourceChangeEvent.POST_CHANGE);
+ //adding the lifecycle listener twice does no harm
+ ((Workspace) workspace).addLifecycleListener(this);
+ if (Policy.DEBUG_AUTO_REFRESH)
+ Policy.debug(RefreshManager.DEBUG_PREFIX + " starting monitor manager."); //$NON-NLS-1$
+ //If not exclusively using polling, create a polling monitor and run it once, to catch
+ //changes that occurred while the native monitor was turned off.
+ if (refreshNeeded)
+ new PollingMonitor(refreshManager).runOnce();
+ }
+
+ /**
+ * Stop the monitoring of resources by all monitors.
+ */
+ public void stop() {
+ workspace.removeResourceChangeListener(this);
+ workspace.getPathVariableManager().removeChangeListener(this);
+ // synchronized: protect the collection during iteration
+ synchronized (registeredMonitors) {
+ for (Iterator i = registeredMonitors.keySet().iterator(); i.hasNext();) {
+ IRefreshMonitor monitor = i.next();
+ monitor.unmonitor(null);
+ }
+ }
+ registeredMonitors.clear();
+ if (Policy.DEBUG_AUTO_REFRESH)
+ Policy.debug(RefreshManager.DEBUG_PREFIX + " stopping monitor manager."); //$NON-NLS-1$
+ pollMonitor.cancel();
+ }
+
+ void unmonitor(IResource resource) {
+ if (resource == null || !isMonitoring(resource))
+ return;
+ synchronized (registeredMonitors) {
+ for (Iterator>> i = registeredMonitors.entrySet().iterator(); i.hasNext();) {
+ Entry> current = i.next();
+ List resources = current.getValue();
+ if ((resources != null) && !resources.isEmpty() && resources.contains(resource)) {
+ current.getKey().unmonitor(resource);
+ resources.remove(resource);
+ }
+ }
+ }
+ if (resource.getType() == IResource.PROJECT)
+ unmonitorLinkedContents((IProject) resource);
+ }
+
+ private void unmonitorLinkedContents(IProject project) {
+ if (!project.isAccessible())
+ return;
+ IResource[] children = null;
+ try {
+ children = project.members();
+ } catch (CoreException e) {
+ Policy.log(IStatus.WARNING, Messages.refresh_refreshErr, e);
+ }
+ if (children != null && children.length > 0)
+ for (int i = 0; i < children.length; i++)
+ if (children[i].isLinked())
+ unmonitor(children[i]);
+ }
+
+ @Override
+ public void resourceChanged(IResourceChangeEvent event) {
+ IResourceDelta delta = event.getDelta();
+ if (delta == null)
+ return;
+ try {
+ delta.accept(this);
+ } catch (CoreException e) {
+ //cannot happen as our visitor doesn't throw exceptions
+ }
+ }
+
+ @Override
+ public boolean visit(IResourceDelta delta) {
+ if (delta.getKind() == IResourceDelta.ADDED) {
+ IResource resource = delta.getResource();
+ if (resource.isLinked())
+ monitor(resource);
+ }
+ if ((delta.getFlags() & IResourceDelta.OPEN) != 0) {
+ IProject project = (IProject) delta.getResource();
+ if (project.isAccessible())
+ monitor(project);
+ }
+ return true;
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/PollingMonitor.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/PollingMonitor.java
new file mode 100644
index 0000000000..c77213609f
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/PollingMonitor.java
@@ -0,0 +1,220 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2015 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM - Initial API and implementation
+ * James Blackburn (Broadcom Corp.) - ongoing development
+ *******************************************************************************/
+package org.eclipse.core.internal.refresh;
+
+import java.util.ArrayList;
+import org.eclipse.core.internal.resources.Resource;
+import org.eclipse.core.internal.utils.Messages;
+import org.eclipse.core.internal.utils.Policy;
+import org.eclipse.core.resources.*;
+import org.eclipse.core.resources.refresh.IRefreshMonitor;
+import org.eclipse.core.runtime.*;
+import org.eclipse.core.runtime.jobs.Job;
+import org.osgi.framework.Bundle;
+
+/**
+ * The PollingMonitor is an IRefreshMonitor that
+ * polls the file system rather than registering natively for call-backs.
+ *
+ * The polling monitor operates in iterations that span multiple invocations
+ * of the job's run method. At the beginning of an iteration, a set of
+ * all resource roots is collected. Each time the job runs, it removes items
+ * from the set and searches for changes for a fixed period of time.
+ * This ensures that the refresh job is broken into very small discrete
+ * operations that do not interrupt the user's main-line activity.
+ *
+ * @since 3.0
+ */
+public class PollingMonitor extends Job implements IRefreshMonitor {
+ /**
+ * The maximum duration of a single polling iteration
+ */
+ private static final long MAX_DURATION = 250;
+ /**
+ * The amount of time that a changed root should remain hot.
+ */
+ private static final long HOT_ROOT_DECAY = 90000;
+ /**
+ * The minimum delay between executions of the polling monitor
+ */
+ private static final long MIN_FREQUENCY = 4000;
+ /**
+ * The roots of resources which should be polled
+ */
+ private final ArrayList resourceRoots;
+ /**
+ * The resources remaining to be refreshed in this iteration
+ */
+ private final ArrayList toRefresh;
+ /**
+ * The root that has most recently been out of sync
+ */
+ private IResource hotRoot;
+ /**
+ * The time the hot root was last refreshed
+ */
+ private long hotRootTime;
+
+ private final RefreshManager refreshManager;
+ /**
+ * True if this job has never been run. False otherwise.
+ */
+ private boolean firstRun = true;
+
+ /**
+ * Creates a new polling monitor.
+ */
+ public PollingMonitor(RefreshManager manager) {
+ super(Messages.refresh_pollJob);
+ this.refreshManager = manager;
+ setPriority(Job.DECORATE);
+ setSystem(true);
+ resourceRoots = new ArrayList();
+ toRefresh = new ArrayList();
+ }
+
+ /**
+ * Add the given root to the list of roots that need to be polled.
+ */
+ public synchronized void monitor(IResource root) {
+ resourceRoots.add(root);
+ schedule(MIN_FREQUENCY);
+ }
+
+ /**
+ * Polls the file system under the root containers for changes.
+ */
+ @Override
+ protected IStatus run(IProgressMonitor monitor) {
+ //sleep until resources plugin has finished starting
+ if (firstRun) {
+ firstRun = false;
+ Bundle bundle = Platform.getBundle(ResourcesPlugin.PI_RESOURCES);
+ long waitStart = System.currentTimeMillis();
+ while (bundle.getState() == Bundle.STARTING) {
+ try {
+ Thread.sleep(10000);
+ } catch (InterruptedException e) {
+ //ignore
+ }
+ //don't wait forever
+ if ((System.currentTimeMillis() - waitStart) > 90000)
+ break;
+ }
+ }
+ long time = System.currentTimeMillis();
+ //check to see if we need to start an iteration
+ if (toRefresh.isEmpty()) {
+ beginIteration();
+ if (Policy.DEBUG_AUTO_REFRESH)
+ Policy.debug(RefreshManager.DEBUG_PREFIX + "New polling iteration on " + toRefresh.size() + " roots"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ final int oldSize = toRefresh.size();
+ if (Policy.DEBUG_AUTO_REFRESH)
+ Policy.debug(RefreshManager.DEBUG_PREFIX + "started polling"); //$NON-NLS-1$
+ //refresh the hot root if applicable
+ if (time - hotRootTime > HOT_ROOT_DECAY)
+ hotRoot = null;
+ else if (hotRoot != null && !monitor.isCanceled())
+ poll(hotRoot);
+ //process roots that have not yet been refreshed this iteration
+ final long loopStart = System.currentTimeMillis();
+ while (!toRefresh.isEmpty()) {
+ if (monitor.isCanceled())
+ break;
+ poll(toRefresh.remove(toRefresh.size() - 1));
+ //stop the iteration if we have exceed maximum duration
+ if (System.currentTimeMillis() - loopStart > MAX_DURATION)
+ break;
+ }
+ time = System.currentTimeMillis() - time;
+ if (Policy.DEBUG_AUTO_REFRESH)
+ Policy.debug(RefreshManager.DEBUG_PREFIX + "polled " + (oldSize - toRefresh.size()) + " roots in " + time + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ //reschedule automatically - shouldRun will cancel if not needed
+ //make sure it doesn't run more than 5% of the time
+ long delay = Math.max(MIN_FREQUENCY, time * 20);
+ //back off even more if there are other jobs running
+ if (!getJobManager().isIdle())
+ delay *= 2;
+ if (Policy.DEBUG_AUTO_REFRESH)
+ Policy.debug(RefreshManager.DEBUG_PREFIX + "rescheduling polling job in: " + delay / 1000 + " seconds"); //$NON-NLS-1$ //$NON-NLS-2$
+ //don't reschedule the job if the resources plugin has been shut down
+ if (Platform.getBundle(ResourcesPlugin.PI_RESOURCES).getState() == Bundle.ACTIVE)
+ schedule(delay);
+ return Status.OK_STATUS;
+ }
+
+ /**
+ * Instructs the polling job to do one complete iteration of all workspace roots, and
+ * then discard itself. This is used when
+ * the refresh manager is first turned on if there is a native monitor installed (which
+ * don't handle changes that occurred while the monitor was turned off).
+ */
+ void runOnce() {
+ synchronized (this) {
+ //add all roots to the refresh list, but not to the real set of roots
+ //this will cause the job to never run again once it has exhausted
+ //the set of roots to refresh
+ IProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects(IContainer.INCLUDE_HIDDEN);
+ for (int i = 0; i < projects.length; i++)
+ toRefresh.add(projects[i]);
+ }
+ schedule(MIN_FREQUENCY);
+ }
+
+ private void poll(IResource resource) {
+ if (resource.isSynchronized(IResource.DEPTH_INFINITE))
+ return;
+ //don't refresh links with no local content
+ if (resource.isLinked() && !((Resource) resource).getStore().fetchInfo().exists())
+ return;
+ //submit refresh request
+ refreshManager.refresh(resource);
+ hotRoot = resource;
+ hotRootTime = System.currentTimeMillis();
+ if (Policy.DEBUG_AUTO_REFRESH)
+ Policy.debug(RefreshManager.DEBUG_PREFIX + "new hot root: " + resource); //$NON-NLS-1$
+ }
+
+ /* (non-Javadoc)
+ * @see Job#shouldRun
+ */
+ @Override
+ public boolean shouldRun() {
+ //only run if there is something to refresh
+ return !resourceRoots.isEmpty() || !toRefresh.isEmpty();
+ }
+
+ /**
+ * Copies the resources to be polled into the list of resources
+ * to refresh this iteration. This method is synchronized to
+ * guard against concurrent access to the resourceRoots field.
+ */
+ private synchronized void beginIteration() {
+ toRefresh.addAll(resourceRoots);
+ if (hotRoot != null)
+ toRefresh.remove(hotRoot);
+ }
+
+ /*
+ * @see org.eclipse.core.resources.refresh.IRefreshMonitor#unmonitor(IContainer)
+ */
+ @Override
+ public synchronized void unmonitor(IResource resource) {
+ if (resource == null)
+ resourceRoots.clear();
+ else
+ resourceRoots.remove(resource);
+ if (resourceRoots.isEmpty())
+ cancel();
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/RefreshJob.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/RefreshJob.java
new file mode 100644
index 0000000000..207e152cb0
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/RefreshJob.java
@@ -0,0 +1,229 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2014 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM - Initial API and implementation
+ * James Blackburn (Broadcom Corp.) - ongoing development
+ *******************************************************************************/
+package org.eclipse.core.internal.refresh;
+
+import java.util.*;
+import org.eclipse.core.internal.localstore.PrefixPool;
+import org.eclipse.core.internal.utils.Messages;
+import org.eclipse.core.internal.utils.Policy;
+import org.eclipse.core.resources.*;
+import org.eclipse.core.runtime.*;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * The RefreshJob class maintains a list of resources that
+ * need to be refreshed, and periodically schedules itself to perform the
+ * refreshes in the background.
+ *
+ * @since 3.0
+ */
+public class RefreshJob extends WorkspaceJob {
+ private static final long UPDATE_DELAY = 200;
+ /**
+ * List of refresh requests. Requests are processed in order from
+ * the end of the list. Requests can be added to either the beginning
+ * or the end of the list depending on whether they are explicit user
+ * requests or background refresh requests.
+ */
+ private final List fRequests;
+
+ /**
+ * The history of path prefixes visited during this refresh job invocation.
+ * This is used to prevent infinite refresh loops caused by symbolic links in the file system.
+ */
+ private PrefixPool pathPrefixHistory, rootPathHistory;
+
+ public RefreshJob() {
+ super(Messages.refresh_jobName);
+ fRequests = new ArrayList(1);
+ }
+
+ /**
+ * Adds the given resource to the set of resources that need refreshing.
+ * Synchronized in order to protect the collection during add.
+ * @param resource
+ */
+ private synchronized void addRequest(IResource resource) {
+ IPath toAdd = resource.getFullPath();
+ for (Iterator it = fRequests.iterator(); it.hasNext();) {
+ IPath request = it.next().getFullPath();
+ //discard any existing requests the same or below the resource to be added
+ if (toAdd.isPrefixOf(request))
+ it.remove();
+ //nothing to do if the resource to be added is a child of an existing request
+ else if (request.isPrefixOf(toAdd))
+ return;
+ }
+ //finally add the new request to the front of the queue
+ fRequests.add(resource);
+ }
+
+ private synchronized void addRequests(List list) {
+ //add requests to the end of the queue
+ fRequests.addAll(0, list);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.runtime.jobs.Job#belongsTo(Object)
+ */
+ @Override
+ public boolean belongsTo(Object family) {
+ return family == ResourcesPlugin.FAMILY_AUTO_REFRESH;
+ }
+
+ /**
+ * This method adds all members at the specified depth from the resource
+ * to the provided list.
+ */
+ private List collectChildrenToDepth(IResource resource, ArrayList children, int depth) {
+ if (resource.getType() == IResource.FILE)
+ return children;
+ IResource[] members;
+ try {
+ members = ((IContainer) resource).members();
+ } catch (CoreException e) {
+ //resource is not accessible - just return what we have
+ return children;
+ }
+ for (int i = 0; i < members.length; i++) {
+ if (members[i].getType() == IResource.FILE)
+ continue;
+ if (depth <= 1)
+ children.add(members[i]);
+ else
+ collectChildrenToDepth(members[i], children, depth - 1);
+ }
+ return children;
+ }
+
+ /**
+ * Returns the path prefixes visited by this job so far.
+ */
+ public PrefixPool getPathPrefixHistory() {
+ if (pathPrefixHistory == null)
+ pathPrefixHistory = new PrefixPool(20);
+ return pathPrefixHistory;
+ }
+
+ /**
+ * Returns the root paths visited by this job so far.
+ */
+ public PrefixPool getRootPathHistory() {
+ if (rootPathHistory == null)
+ rootPathHistory = new PrefixPool(20);
+ return rootPathHistory;
+ }
+
+ /**
+ * Returns the next item to refresh, or null if there are no requests
+ */
+ private synchronized IResource nextRequest() {
+ // synchronized: in order to atomically obtain and clear requests
+ int len = fRequests.size();
+ if (len == 0)
+ return null;
+ return fRequests.remove(len - 1);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.resources.refresh.IRefreshResult#refresh
+ */
+ public void refresh(IResource resource) {
+ if (resource == null)
+ return;
+ addRequest(resource);
+ schedule(UPDATE_DELAY);
+ }
+
+ /* (non-Javadoc)
+ * @see WorkspaceJob#runInWorkspace
+ */
+ @Override
+ public IStatus runInWorkspace(IProgressMonitor monitor) {
+ long start = System.currentTimeMillis();
+ String msg = Messages.refresh_refreshErr;
+ MultiStatus errors = new MultiStatus(ResourcesPlugin.PI_RESOURCES, 1, msg, null);
+ long longestRefresh = 0;
+ try {
+ if (Policy.DEBUG_AUTO_REFRESH)
+ Policy.debug(RefreshManager.DEBUG_PREFIX + " starting refresh job"); //$NON-NLS-1$
+ int refreshCount = 0;
+ int depth = 2;
+ monitor.beginTask("", IProgressMonitor.UNKNOWN); //$NON-NLS-1$
+ IResource toRefresh;
+ while ((toRefresh = nextRequest()) != null) {
+ if (monitor.isCanceled())
+ throw new OperationCanceledException();
+ try {
+ refreshCount++;
+ long refreshTime = -System.currentTimeMillis();
+ toRefresh.refreshLocal(1000 + depth, Policy.subMonitorFor(monitor, 0));
+ refreshTime += System.currentTimeMillis();
+ if (refreshTime > longestRefresh)
+ longestRefresh = refreshTime;
+ //show occasional progress
+ if (refreshCount % 100 == 0)
+ monitor.subTask(NLS.bind(Messages.refresh_task, Integer.toString(fRequests.size())));
+ if (refreshCount % 1000 == 0) {
+ //be polite to other threads (no effect on some platforms)
+ Thread.yield();
+ //throttle depth if it takes too long
+ if (longestRefresh > 2000 && depth > 1) {
+ depth = 1;
+ }
+ if (longestRefresh < 1000) {
+ depth *= 2;
+ }
+ longestRefresh = 0;
+ }
+ addRequests(collectChildrenToDepth(toRefresh, new ArrayList(), depth));
+ } catch (CoreException e) {
+ errors.merge(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, 1, errors.getMessage(), e));
+ }
+ }
+ } finally {
+ pathPrefixHistory = null;
+ rootPathHistory = null;
+ monitor.done();
+ if (Policy.DEBUG_AUTO_REFRESH)
+ Policy.debug(RefreshManager.DEBUG_PREFIX + " finished refresh job in: " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ if (!errors.isOK())
+ return errors;
+ return Status.OK_STATUS;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.runtime.jobs.Job#shouldRun()
+ */
+ @Override
+ public synchronized boolean shouldRun() {
+ return !fRequests.isEmpty();
+ }
+
+ /**
+ * Starts the refresh job
+ */
+ public void start() {
+ if (Policy.DEBUG_AUTO_REFRESH)
+ Policy.debug(RefreshManager.DEBUG_PREFIX + " enabling auto-refresh"); //$NON-NLS-1$
+ }
+
+ /**
+ * Stops the refresh job
+ */
+ public void stop() {
+ if (Policy.DEBUG_AUTO_REFRESH)
+ Policy.debug(RefreshManager.DEBUG_PREFIX + " disabling auto-refresh"); //$NON-NLS-1$
+ cancel();
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/RefreshManager.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/RefreshManager.java
new file mode 100644
index 0000000000..07dfc861a2
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/RefreshManager.java
@@ -0,0 +1,117 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2015 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM - Initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.refresh;
+
+import org.eclipse.core.internal.resources.IManager;
+import org.eclipse.core.resources.*;
+import org.eclipse.core.resources.refresh.IRefreshMonitor;
+import org.eclipse.core.resources.refresh.IRefreshResult;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.Preferences;
+import org.eclipse.core.runtime.Preferences.PropertyChangeEvent;
+
+/**
+ * Manages auto-refresh functionality, including maintaining the active
+ * set of monitors and controlling the job that performs periodic refreshes
+ * on out of sync resources.
+ *
+ * @since 3.0
+ */
+public class RefreshManager implements IRefreshResult, IManager, Preferences.IPropertyChangeListener {
+ public static final String DEBUG_PREFIX = "Auto-refresh: "; //$NON-NLS-1$
+ MonitorManager monitors;
+ private RefreshJob refreshJob;
+
+ /**
+ * The workspace.
+ */
+ private IWorkspace workspace;
+
+ public RefreshManager(IWorkspace workspace) {
+ this.workspace = workspace;
+ }
+
+ /*
+ * Starts or stops auto-refresh depending on the auto-refresh preference.
+ */
+ protected void manageAutoRefresh(boolean enabled) {
+ //do nothing if we have already shutdown
+ if (refreshJob == null)
+ return;
+ if (enabled) {
+ refreshJob.start();
+ monitors.start();
+ } else {
+ refreshJob.stop();
+ monitors.stop();
+ }
+ }
+
+ @Override
+ public void monitorFailed(IRefreshMonitor monitor, IResource resource) {
+ monitors.monitorFailed(monitor, resource);
+ }
+
+ /**
+ * Checks for changes to the PREF_AUTO_UPDATE property.
+ * @see org.eclipse.core.runtime.Preferences.IPropertyChangeListener#propertyChange(Preferences.PropertyChangeEvent)
+ */
+ @Deprecated
+ @Override
+ public void propertyChange(PropertyChangeEvent event) {
+ String property = event.getProperty();
+ if (ResourcesPlugin.PREF_AUTO_REFRESH.equals(property)) {
+ Preferences preferences = ResourcesPlugin.getPlugin().getPluginPreferences();
+ boolean autoRefresh = preferences.getBoolean(ResourcesPlugin.PREF_AUTO_REFRESH);
+ manageAutoRefresh(autoRefresh);
+ }
+ }
+
+ @Override
+ public void refresh(IResource resource) {
+ //do nothing if we have already shutdown
+ if (refreshJob != null)
+ refreshJob.refresh(resource);
+ }
+
+ /**
+ * Shuts down the refresh manager. This only happens when
+ * the resources plugin is going away.
+ */
+ @Override
+ public void shutdown(IProgressMonitor monitor) {
+ ResourcesPlugin.getPlugin().getPluginPreferences().removePropertyChangeListener(this);
+ if (monitors != null) {
+ monitors.stop();
+ monitors = null;
+ }
+ if (refreshJob != null) {
+ refreshJob.stop();
+ refreshJob = null;
+ }
+ }
+
+ /**
+ * Initializes the refresh manager. This does a minimal amount of work
+ * if auto-refresh is turned off.
+ */
+ @Override
+ public void startup(IProgressMonitor monitor) {
+ Preferences preferences = ResourcesPlugin.getPlugin().getPluginPreferences();
+ preferences.addPropertyChangeListener(this);
+
+ refreshJob = new RefreshJob();
+ monitors = new MonitorManager(workspace, this);
+ boolean autoRefresh = preferences.getBoolean(ResourcesPlugin.PREF_AUTO_REFRESH);
+ if (autoRefresh)
+ manageAutoRefresh(autoRefresh);
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/AliasManager.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/AliasManager.java
new file mode 100644
index 0000000000..3eb3178faf
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/AliasManager.java
@@ -0,0 +1,767 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2015 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * manklu@web.de - fix for bug 156082
+ * Bert Vingerhoets - fix for bug 169975
+ * Serge Beauchamp (Freescale Semiconductor) - [229633] Fix Concurency Exception
+ * Sergey Prigogin (Google) - [338010] Resource.createLink() does not preserve symbolic links
+ *******************************************************************************/
+package org.eclipse.core.internal.resources;
+
+import java.net.URI;
+import java.util.*;
+import org.eclipse.core.filesystem.EFS;
+import org.eclipse.core.filesystem.IFileStore;
+import org.eclipse.core.internal.events.ILifecycleListener;
+import org.eclipse.core.internal.events.LifecycleEvent;
+import org.eclipse.core.internal.localstore.FileSystemResourceManager;
+import org.eclipse.core.internal.utils.*;
+import org.eclipse.core.resources.*;
+import org.eclipse.core.runtime.*;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * An alias is a resource that occupies the same file system location as another
+ * resource in the workspace. When a resource is modified in a way that affects
+ * the file on disk, all aliases need to be updated. This class is used to
+ * maintain data structures for quickly computing the set of aliases for a given
+ * resource, and for efficiently updating all aliases when a resource changes on
+ * disk.
+ *
+ * The approach for computing aliases is optimized for alias-free workspaces and
+ * alias-free projects. That is, if the workspace contains no aliases, then
+ * updating should be very quick. If a resource is changed in a project that
+ * contains no aliases, it should also be very fast.
+ *
+ * The data structures maintained by the alias manager can be seen as a cache,
+ * that is, they store no information that cannot be recomputed from other
+ * available information. On shutdown, the alias manager discards all state; on
+ * startup, the alias manager eagerly rebuilds its state. The reasoning is
+ * that it's better to incur this cost on startup than on the first attempt to
+ * modify a resource. After startup, the state is updated incrementally on the
+ * following occasions:
+ * - when projects are deleted, opened, closed, or moved
+ * - when linked resources are created, deleted, or moved.
+ */
+public class AliasManager implements IManager, ILifecycleListener, IResourceChangeListener {
+ public class AddToCollectionDoit implements Doit {
+ Collection collection;
+
+ @Override
+ public void doit(IResource resource) {
+ collection.add(resource);
+ }
+
+ public void setCollection(Collection collection) {
+ this.collection = collection;
+ }
+ }
+
+ interface Doit {
+ public void doit(IResource resource);
+ }
+
+ class FindAliasesDoit implements Doit {
+ private int aliasType;
+ private IPath searchPath;
+
+ @Override
+ public void doit(IResource match) {
+ //don't record the resource we're computing aliases against as a match
+ if (match.getFullPath().isPrefixOf(searchPath))
+ return;
+ IPath aliasPath = null;
+ switch (match.getType()) {
+ case IResource.PROJECT :
+ //first check if there is a linked resource that blocks the project location
+ if (suffix.segmentCount() > 0) {
+ IResource testResource = ((IProject) match).findMember(suffix.segment(0));
+ if (testResource != null && testResource.isLinked())
+ return;
+ }
+ //there is an alias under this project
+ aliasPath = match.getFullPath().append(suffix);
+ break;
+ case IResource.FOLDER :
+ aliasPath = match.getFullPath().append(suffix);
+ break;
+ case IResource.FILE :
+ if (suffix.segmentCount() == 0)
+ aliasPath = match.getFullPath();
+ break;
+ }
+ if (aliasPath != null)
+ if (aliasType == IResource.FILE) {
+ aliases.add(workspace.getRoot().getFile(aliasPath));
+ } else {
+ if (aliasPath.segmentCount() == 1)
+ aliases.add(workspace.getRoot().getProject(aliasPath.lastSegment()));
+ else
+ aliases.add(workspace.getRoot().getFolder(aliasPath));
+ }
+ }
+
+ /**
+ * Sets the resource that we are searching for aliases for.
+ */
+ public void setSearchAlias(IResource aliasResource) {
+ this.aliasType = aliasResource.getType();
+ this.searchPath = aliasResource.getFullPath();
+ }
+ }
+
+ /**
+ * Maintains a mapping of FileStore->IResource, such that multiple resources
+ * mapped from the same location are tolerated.
+ */
+ class LocationMap {
+ /**
+ * Map of FileStore->IResource OR FileStore->ArrayList of (IResource)
+ */
+ private final SortedMap map = new TreeMap(getComparator());
+
+ /**
+ * Adds the given resource to the map, keyed by the given location.
+ * Returns true if a new entry was added, and false otherwise.
+ */
+ public boolean add(IFileStore location, IResource resource) {
+ Object oldValue = map.get(location);
+ if (oldValue == null) {
+ map.put(location, resource);
+ return true;
+ }
+ if (oldValue instanceof IResource) {
+ if (resource.equals(oldValue))
+ return false;//duplicate
+ ArrayList newValue = new ArrayList(2);
+ newValue.add(oldValue);
+ newValue.add(resource);
+ map.put(location, newValue);
+ return true;
+ }
+ @SuppressWarnings("unchecked")
+ ArrayList list = (ArrayList) oldValue;
+ if (list.contains(resource))
+ return false;//duplicate
+ list.add(resource);
+ return true;
+ }
+
+ /**
+ * Method clear.
+ */
+ public void clear() {
+ map.clear();
+ }
+
+ /**
+ * Invoke the given doit for every resource whose location has the
+ * given location as a prefix.
+ */
+ public void matchingPrefixDo(IFileStore prefix, Doit doit) {
+ SortedMap matching;
+ IFileStore prefixParent = prefix.getParent();
+ if (prefixParent != null) {
+ //endPoint is the smallest possible path greater than the prefix that doesn't
+ //match the prefix
+ IFileStore endPoint = prefixParent.getChild(prefix.getName() + "\0"); //$NON-NLS-1$
+ matching = map.subMap(prefix, endPoint);
+ } else {
+ matching = map;
+ }
+ for (Iterator it = matching.values().iterator(); it.hasNext();) {
+ Object value = it.next();
+ if (value == null)
+ return;
+ if (value instanceof List) {
+ @SuppressWarnings("unchecked")
+ Iterator duplicates = ((List) value).iterator();
+ while (duplicates.hasNext())
+ doit.doit(duplicates.next());
+ } else {
+ doit.doit((IResource) value);
+ }
+ }
+ }
+
+ /**
+ * Invoke the given doit for every resource that matches the given
+ * location.
+ */
+ public void matchingResourcesDo(IFileStore location, Doit doit) {
+ Object value = map.get(location);
+ if (value == null)
+ return;
+ if (value instanceof List) {
+ @SuppressWarnings("unchecked")
+ Iterator duplicates = ((List) value).iterator();
+ while (duplicates.hasNext())
+ doit.doit(duplicates.next());
+ } else {
+ doit.doit((IResource) value);
+ }
+ }
+
+ /**
+ * Calls the given doit with the project of every resource in the map
+ * whose location overlaps another resource in the map.
+ */
+ public void overLappingResourcesDo(Doit doit) {
+ Iterator> entries = map.entrySet().iterator();
+ IFileStore previousStore = null;
+ IResource previousResource = null;
+ while (entries.hasNext()) {
+ Map.Entry current = entries.next();
+ //value is either single resource or List of resources
+ IFileStore currentStore = current.getKey();
+ IResource currentResource = null;
+ Object value = current.getValue();
+ if (value instanceof List) {
+ //if there are several then they're all overlapping
+ @SuppressWarnings("unchecked")
+ Iterator duplicates = ((List) value).iterator();
+ while (duplicates.hasNext())
+ doit.doit(duplicates.next().getProject());
+ } else {
+ //value is a single resource
+ currentResource = (IResource) value;
+ }
+ if (previousStore != null) {
+ //check for overlap with previous
+ //Note: previous is always shorter due to map sorting rules
+ if (previousStore.isParentOf(currentStore)) {
+ //resources will be null if they were in a list, in which case
+ //they've already been passed to the doit
+ if (previousResource != null) {
+ doit.doit(previousResource.getProject());
+ //null out previous resource so we don't call doit twice with same resource
+ previousResource = null;
+ }
+ if (currentResource != null)
+ doit.doit(currentResource.getProject());
+ //keep iterating with the same previous store because there may be more overlaps
+ continue;
+ }
+ }
+ previousStore = currentStore;
+ previousResource = currentResource;
+ }
+ }
+
+ /**
+ * Removes the given location from the map. Returns true if anything
+ * was actually removed, and false otherwise.
+ */
+ public boolean remove(IFileStore location, IResource resource) {
+ Object oldValue = map.get(location);
+ if (oldValue == null)
+ return false;
+ if (oldValue instanceof IResource) {
+ if (resource.equals(oldValue)) {
+ map.remove(location);
+ return true;
+ }
+ return false;
+ }
+ @SuppressWarnings("unchecked")
+ ArrayList list = (ArrayList) oldValue;
+ boolean wasRemoved = list.remove(resource);
+ if (list.size() == 0)
+ map.remove(location);
+ return wasRemoved;
+ }
+ }
+
+ /**
+ * Doit convenience class for adding items to a list
+ */
+ private final AddToCollectionDoit addToCollection = new AddToCollectionDoit();
+
+ /**
+ * The set of IProjects that have aliases.
+ */
+ protected final Set aliasedProjects = new HashSet();
+
+ /**
+ * A temporary set of aliases. Used during computeAliases, but maintained
+ * as a field as an optimization to prevent recreating the set.
+ */
+ protected final HashSet aliases = new HashSet();
+
+ /**
+ * The set of resources that have had structure changes that might
+ * invalidate the locations map or aliased projects set. These will be
+ * updated incrementally on the next alias request.
+ */
+ private final Set changedLinks = new HashSet();
+
+ /**
+ * This flag is true when projects have been created or deleted and the
+ * location map has not been updated accordingly.
+ */
+ private boolean changedProjects = false;
+
+ /**
+ * The Doit class used for finding aliases.
+ */
+ private final FindAliasesDoit findAliases = new FindAliasesDoit();
+
+ /**
+ * This maps IFileStore ->IResource, associating a file system location
+ * with the projects and/or linked resources that are rooted at that location.
+ */
+ protected final LocationMap locationsMap = new LocationMap();
+ /**
+ * The total number of resources in the workspace that are not in the default
+ * location. This includes all linked resources, including linked resources
+ * that don't currently have valid locations due to an undefined path variable.
+ * This also includes projects that are not in their default location.
+ * This value is used as a quick optimization, because a workspace with
+ * all resources in their default locations cannot have any aliases.
+ */
+ private int nonDefaultResourceCount = 0;
+
+ /**
+ * The suffix object is also used only during the computeAliases method.
+ * In this case it is a field because it is referenced from an inner class
+ * and we want to avoid creating a pointer array. It is public to eliminate
+ * the need for synthetic accessor methods.
+ */
+ public IPath suffix;
+
+ /** the workspace */
+ protected final Workspace workspace;
+
+ public AliasManager(Workspace workspace) {
+ this.workspace = workspace;
+ }
+
+ private void addToLocationsMap(IProject project) {
+ IFileStore location = ((Resource) project).getStore();
+ if (location != null)
+ locationsMap.add(location, project);
+ ProjectDescription description = ((Project) project).internalGetDescription();
+ if (description == null)
+ return;
+ if (description.getLocationURI() != null)
+ nonDefaultResourceCount++;
+ HashMap links = description.getLinks();
+ if (links == null)
+ return;
+ for (LinkDescription linkDesc : links.values()) {
+ IResource link = project.findMember(linkDesc.getProjectRelativePath());
+ if (link != null) {
+ try {
+ URI locationURI = linkDesc.getLocationURI();
+ locationURI = FileUtil.canonicalURI(locationURI);
+ locationURI = link.getPathVariableManager().resolveURI(locationURI);
+ addToLocationsMap(link, EFS.getStore(locationURI));
+ } catch (CoreException e) {
+ //ignore links with invalid locations
+ }
+ }
+ }
+ }
+
+ private void addToLocationsMap(IResource link, IFileStore location) {
+ if (location != null && !link.isVirtual())
+ if (locationsMap.add(location, link))
+ nonDefaultResourceCount++;
+ }
+
+ /**
+ * Builds the table of aliased projects from scratch.
+ */
+ private void buildAliasedProjectsSet() {
+ aliasedProjects.clear();
+ //if there are no resources in non-default locations then there can't be any aliased projects
+ if (nonDefaultResourceCount <= 0)
+ return;
+ //for every resource that overlaps another, marked its project as aliased
+ addToCollection.setCollection(aliasedProjects);
+ locationsMap.overLappingResourcesDo(addToCollection);
+ }
+
+ /**
+ * Builds the table of resource locations from scratch. Also computes an
+ * initial value for the linked resource counter.
+ */
+ private void buildLocationsMap() {
+ locationsMap.clear();
+ nonDefaultResourceCount = 0;
+ //build table of IPath (file system location) -> IResource (project or linked resource)
+ IProject[] projects = workspace.getRoot().getProjects(IContainer.INCLUDE_HIDDEN);
+ for (int i = 0; i < projects.length; i++)
+ if (projects[i].isAccessible())
+ addToLocationsMap(projects[i]);
+ }
+
+ /**
+ * A project alias needs updating. If the project location has been deleted,
+ * then the project should be deleted from the workspace. This differs
+ * from the refresh local strategy, but operations performed from within
+ * the workspace must never leave a resource out of sync.
+ * @param project The project to check for deletion
+ * @param location The project location
+ * @return true if the project has been deleted, and false otherwise
+ * @exception CoreException
+ */
+ private boolean checkDeletion(Project project, IFileStore location) throws CoreException {
+ if (project.exists() && !location.fetchInfo().exists()) {
+ //perform internal deletion of project from workspace tree because
+ // it is already deleted from disk and we can't acquire a different
+ //scheduling rule in this context (none is needed because we are
+ //within scope of the workspace lock)
+ Assert.isTrue(workspace.getWorkManager().getLock().getDepth() > 0);
+ project.deleteResource(false, null);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns all aliases of the given resource, or null if there are none.
+ */
+ public IResource[] computeAliases(final IResource resource, IFileStore location) {
+ //nothing to do if we are or were in an alias-free workspace or project
+ if (hasNoAliases(resource))
+ return null;
+
+ aliases.clear();
+ internalComputeAliases(resource, location);
+ int size = aliases.size();
+ if (size == 0)
+ return null;
+ return aliases.toArray(new IResource[size]);
+ }
+
+ /**
+ * Returns all resources pointing to the given location, or an empty array if there are none.
+ */
+ public IResource[] findResources(IFileStore location) {
+ final ArrayList resources = new ArrayList();
+ locationsMap.matchingResourcesDo(location, new Doit() {
+ @Override
+ public void doit(IResource resource) {
+ resources.add(resource);
+ }
+ });
+ return resources.toArray(new IResource[0]);
+ }
+
+ /**
+ * Returns all aliases of this resource, and any aliases of subtrees of this
+ * resource. Returns null if no aliases are found.
+ */
+ private void computeDeepAliases(IResource resource, IFileStore location) {
+ //if the location is invalid then there won't be any aliases to update
+ if (location == null)
+ return;
+ //get the normal aliases (resources rooted in parent locations)
+ internalComputeAliases(resource, location);
+ //get all resources rooted below this resource's location
+ addToCollection.setCollection(aliases);
+ locationsMap.matchingPrefixDo(location, addToCollection);
+ //if this is a project, get all resources rooted below links in this project
+ if (resource.getType() == IResource.PROJECT) {
+ try {
+ IResource[] members = ((IProject) resource).members();
+ final FileSystemResourceManager localManager = workspace.getFileSystemManager();
+ for (int i = 0; i < members.length; i++) {
+ if (members[i].isLinked()) {
+ IFileStore linkLocation = localManager.getStore(members[i]);
+ if (linkLocation != null)
+ locationsMap.matchingPrefixDo(linkLocation, addToCollection);
+ }
+ }
+ } catch (CoreException e) {
+ //skip inaccessible projects
+ }
+ }
+ }
+
+ /**
+ * Returns the comparator to use when sorting the locations map. Comparison
+ * is based on segments, so that paths with the most segments in common will
+ * always be adjacent. This is equivalent to the natural order on the path
+ * strings, with the extra condition that the path separator is ordered
+ * before all other characters. (Ex: "/foo" < "/foo/zzz" < "/fooaaa").
+ */
+ Comparator getComparator() {
+ return new Comparator() {
+ @Override
+ public int compare(IFileStore store1, IFileStore store2) {
+ //scheme takes precedence over all else
+ int compare = compareStringOrNull(store1.getFileSystem().getScheme(), store2.getFileSystem().getScheme());
+ if (compare != 0)
+ return compare;
+ // compare based on URI path segment values
+ final URI uri1;
+ final URI uri2;
+ try {
+ uri1 = store1.toURI();
+ uri2 = store2.toURI();
+ } catch (Exception e) {
+ //protect against misbehaving 3rd party code in file system implementations
+ Policy.log(e);
+ return 1;
+ }
+
+ // compare hosts
+ compare = compareStringOrNull(uri1.getHost(), uri2.getHost());
+ if (compare != 0)
+ return compare;
+ // compare user infos
+ compare = compareStringOrNull(uri1.getUserInfo(), uri2.getUserInfo());
+ if (compare != 0)
+ return compare;
+ // compare ports
+ int port1 = uri1.getPort();
+ int port2 = uri2.getPort();
+ if (port1 != port2)
+ return port1 - port2;
+
+ IPath path1 = new Path(uri1.getPath());
+ IPath path2 = new Path(uri2.getPath());
+ // compare devices
+ compare = compareStringOrNull(path1.getDevice(), path2.getDevice());
+ if (compare != 0)
+ return compare;
+ // compare segments
+ int segmentCount1 = path1.segmentCount();
+ int segmentCount2 = path2.segmentCount();
+ for (int i = 0; (i < segmentCount1) && (i < segmentCount2); i++) {
+ compare = path1.segment(i).compareTo(path2.segment(i));
+ if (compare != 0)
+ return compare;
+ }
+ //all segments are equal, so compare based on number of segments
+ compare = segmentCount1 - segmentCount2;
+ if (compare != 0)
+ return compare;
+ //same number of segments, so compare query
+ return compareStringOrNull(uri1.getQuery(), uri2.getQuery());
+ }
+
+ /**
+ * Compares two strings that are possibly null.
+ */
+ private int compareStringOrNull(String string1, String string2) {
+ if (string1 == null) {
+ if (string2 == null)
+ return 0;
+ return 1;
+ }
+ if (string2 == null)
+ return -1;
+ return string1.compareTo(string2);
+
+ }
+ };
+ }
+
+ @Override
+ public void handleEvent(LifecycleEvent event) {
+ /*
+ * We can't determine the end state for most operations because they may
+ * fail after we receive pre-notification. In these cases, we remember
+ * the invalidated resources and recompute their state lazily on the
+ * next alias request.
+ */
+ switch (event.kind) {
+ case LifecycleEvent.PRE_LINK_CHANGE :
+ case LifecycleEvent.PRE_LINK_DELETE :
+ Resource link = (Resource) event.resource;
+ if (link.isLinked())
+ removeFromLocationsMap(link, link.getStore());
+ //fall through
+ case LifecycleEvent.PRE_FILTER_ADD :
+ changedLinks.add(event.resource);
+ break;
+ case LifecycleEvent.PRE_FILTER_REMOVE :
+ changedLinks.add(event.resource);
+ break;
+ case LifecycleEvent.PRE_LINK_CREATE :
+ changedLinks.add(event.resource);
+ break;
+ case LifecycleEvent.PRE_LINK_COPY :
+ changedLinks.add(event.newResource);
+ break;
+ case LifecycleEvent.PRE_LINK_MOVE :
+ link = (Resource) event.resource;
+ if (link.isLinked())
+ removeFromLocationsMap(link, link.getStore());
+ changedLinks.add(event.newResource);
+ break;
+ }
+ }
+
+ /**
+ * Returns true if this resource is guaranteed to have no aliases, and false
+ * otherwise.
+ */
+ private boolean hasNoAliases(final IResource resource) {
+ //check if we're in an aliased project or workspace before updating structure changes. In the
+ //deletion case, we need to know if the resource was in an aliased project *before* deletion.
+ IProject project = resource.getProject();
+ boolean noAliases = !aliasedProjects.contains(project);
+
+ //now update any structure changes and check again if an update is needed
+ if (hasStructureChanges()) {
+ updateStructureChanges();
+ noAliases &= nonDefaultResourceCount <= 0 || !aliasedProjects.contains(project);
+ }
+ return noAliases;
+ }
+
+ /**
+ * Returns whether there are any structure changes that we have not yet processed.
+ */
+ private boolean hasStructureChanges() {
+ return changedProjects || !changedLinks.isEmpty();
+ }
+
+ /**
+ * Computes the aliases of the given resource at the given location, and
+ * adds them to the "aliases" collection.
+ */
+ private void internalComputeAliases(IResource resource, IFileStore location) {
+ IFileStore searchLocation = location;
+ if (searchLocation == null)
+ searchLocation = ((Resource) resource).getStore();
+ //if the location is invalid then there won't be any aliases to update
+ if (searchLocation == null)
+ return;
+
+ suffix = Path.EMPTY;
+ findAliases.setSearchAlias(resource);
+ /*
+ * Walk up the location segments for this resource, looking for a
+ * resource with a matching location. All matches are then added to the
+ * "aliases" set.
+ */
+ do {
+ locationsMap.matchingResourcesDo(searchLocation, findAliases);
+ suffix = new Path(searchLocation.getName()).append(suffix);
+ searchLocation = searchLocation.getParent();
+ } while (searchLocation != null);
+ }
+
+ private void removeFromLocationsMap(IResource link, IFileStore location) {
+ if (location != null)
+ if (locationsMap.remove(location, link))
+ nonDefaultResourceCount--;
+ }
+
+ @Override
+ public void resourceChanged(IResourceChangeEvent event) {
+ final IResourceDelta delta = event.getDelta();
+ if (delta == null)
+ return;
+ //invalidate location map if there are added or removed projects.
+ if (delta.getAffectedChildren(IResourceDelta.ADDED | IResourceDelta.REMOVED, IContainer.INCLUDE_HIDDEN).length > 0)
+ changedProjects = true;
+
+ // invalidate location map if any project has the description changed
+ // or was closed/opened
+ IResourceDelta[] changed = delta.getAffectedChildren(IResourceDelta.CHANGED, IContainer.INCLUDE_HIDDEN);
+ for (int i = 0; i < changed.length; i++) {
+ if ((changed[i].getFlags() & IResourceDelta.DESCRIPTION) == IResourceDelta.DESCRIPTION || (changed[i].getFlags() & IResourceDelta.OPEN) == IResourceDelta.OPEN) {
+ changedProjects = true;
+ break;
+ }
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see IManager#shutdown(IProgressMonitor)
+ */
+ @Override
+ public void shutdown(IProgressMonitor monitor) {
+ workspace.removeResourceChangeListener(this);
+ locationsMap.clear();
+ }
+
+ /* (non-Javadoc)
+ * @see IManager#startup(IProgressMonitor)
+ */
+ @Override
+ public void startup(IProgressMonitor monitor) {
+ workspace.addLifecycleListener(this);
+ workspace.addResourceChangeListener(this, IResourceChangeEvent.POST_CHANGE);
+ buildLocationsMap();
+ buildAliasedProjectsSet();
+ }
+
+ /**
+ * The file underlying the given resource has changed on disk. Compute all
+ * aliases for this resource and update them. This method will not attempt
+ * to incur any units of work on the given progress monitor, but it may
+ * update the subtask to reflect what aliases are being updated.
+ * @param resource the resource to compute aliases for
+ * @param location the file system location of the resource (passed as a
+ * parameter because in the project deletion case the resource is no longer
+ * accessible at time of update).
+ * @param depth whether to search for aliases on all children of the given
+ * resource. Only depth ZERO and INFINITE are used.
+ */
+ @SuppressWarnings({"unchecked"})
+ public void updateAliases(IResource resource, IFileStore location, int depth, IProgressMonitor monitor) throws CoreException {
+ if (hasNoAliases(resource))
+ return;
+ aliases.clear();
+ if (depth == IResource.DEPTH_ZERO)
+ internalComputeAliases(resource, location);
+ else
+ computeDeepAliases(resource, location);
+ if (aliases.size() == 0)
+ return;
+ FileSystemResourceManager localManager = workspace.getFileSystemManager();
+ HashSet aliasesCopy = (HashSet) aliases.clone();
+ for (Iterator it = aliasesCopy.iterator(); it.hasNext();) {
+ IResource alias = it.next();
+ monitor.subTask(NLS.bind(Messages.links_updatingDuplicate, alias.getFullPath()));
+ if (alias.getType() == IResource.PROJECT) {
+ if (checkDeletion((Project) alias, location))
+ continue;
+ //project did not require deletion, so fall through below and refresh it
+ }
+ if (!((Resource) alias).isFiltered())
+ localManager.refresh(alias, IResource.DEPTH_INFINITE, false, null);
+ }
+ }
+
+ /**
+ * Process any structural changes that have occurred since the last alias
+ * request.
+ */
+ private void updateStructureChanges() {
+ boolean hadChanges = false;
+ if (changedProjects) {
+ //if a project is added or removed, just recompute the whole world
+ changedProjects = false;
+ hadChanges = true;
+ buildLocationsMap();
+ } else {
+ //incrementally update location map for changed links
+ for (Iterator it = changedLinks.iterator(); it.hasNext();) {
+ IResource resource = it.next();
+ hadChanges = true;
+ if (!resource.isAccessible())
+ continue;
+ if (resource.isLinked())
+ addToLocationsMap(resource, ((Resource) resource).getStore());
+ }
+ }
+ changedLinks.clear();
+ if (hadChanges)
+ buildAliasedProjectsSet();
+ changedProjects = false;
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/BuildConfiguration.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/BuildConfiguration.java
new file mode 100644
index 0000000000..743f606e67
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/BuildConfiguration.java
@@ -0,0 +1,138 @@
+/*******************************************************************************
+ * Copyright (c) 2010, 2015 Broadcom Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Broadcom Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.resources;
+
+import org.eclipse.core.resources.IBuildConfiguration;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.PlatformObject;
+
+/**
+ * Concrete implementation of a build configuration.
+ *
+ * This class can both be used as a real build configuration in a project.
+ * As well as the reference to a build configuration in another project.
+ *
+ *
+ * When being used as a reference, core.resources must call
+ * {@link #getBuildConfig()} to dereference the build configuration to the
+ * the actual build configuration on the referenced project.
+ *
+ */
+public class BuildConfiguration extends PlatformObject implements IBuildConfiguration {
+
+ /** Project on which this build configuration is set */
+ private final IProject project;
+ /** Unique human readable name of the configuration in the project */
+ private final String name;
+
+ public BuildConfiguration(IProject project) {
+ this(project, IBuildConfiguration.DEFAULT_CONFIG_NAME);
+ }
+
+ public BuildConfiguration(IProject project, String configName) {
+ this.project = project;
+ this.name = configName;
+ }
+
+ /**
+ * @return the concrete build configuration referred to by this IBuildConfiguration
+ * when it's being used as a reference
+ */
+ public IBuildConfiguration getBuildConfig() throws CoreException {
+ return project.getBuildConfig(name);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see IBuildConfiguration#getName()
+ */
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see IBuildConfiguration#getProject()
+ */
+ @Override
+ public IProject getProject() {
+ return project;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ BuildConfiguration other = (BuildConfiguration) obj;
+ if (name == null) {
+ if (other.name != null)
+ return false;
+ } else if (!name.equals(other.name))
+ return false;
+ if (project == null) {
+ if (other.project != null)
+ return false;
+ } else if (!project.equals(other.project))
+ return false;
+ return true;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((name == null) ? 0 : name.hashCode());
+ result = prime * result + ((project == null) ? 0 : project.hashCode());
+ return result;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ StringBuffer result = new StringBuffer();
+ if (project != null)
+ result.append(project.getName());
+ else
+ result.append("?"); //$NON-NLS-1$
+ result.append(";"); //$NON-NLS-1$
+ if (name != null)
+ result.append(" [").append(name).append(']'); //$NON-NLS-1$
+ else
+ result.append(" [active]"); //$NON-NLS-1$
+ return result.toString();
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public T getAdapter(Class adapter) {
+ if (adapter.isInstance(project))
+ return (T) project;
+ return super.getAdapter(adapter);
+ }
+
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/CharsetDeltaJob.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/CharsetDeltaJob.java
new file mode 100644
index 0000000000..507d268fbc
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/CharsetDeltaJob.java
@@ -0,0 +1,214 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2015 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * James Blackburn (Broadcom Corp.) - ongoing development
+ *******************************************************************************/
+package org.eclipse.core.internal.resources;
+
+import org.eclipse.core.internal.utils.*;
+import org.eclipse.core.internal.watson.*;
+import org.eclipse.core.resources.*;
+import org.eclipse.core.runtime.*;
+import org.eclipse.core.runtime.content.IContentTypeManager;
+import org.eclipse.core.runtime.content.IContentTypeManager.ContentTypeChangeEvent;
+import org.eclipse.core.runtime.jobs.Job;
+import org.osgi.framework.Bundle;
+
+/**
+ * Detects changes to content types/project preferences and
+ * broadcasts any corresponding encoding changes as resource deltas.
+ */
+
+public class CharsetDeltaJob extends Job implements IContentTypeManager.IContentTypeChangeListener {
+
+ // this is copied in the runtime tests - if changed here, has to be changed there too
+ public final static String FAMILY_CHARSET_DELTA = ResourcesPlugin.PI_RESOURCES + "charsetJobFamily"; //$NON-NLS-1$
+
+ interface ICharsetListenerFilter {
+
+ /**
+ * Returns the path for the node in the tree we are interested in. Returns null
+ * if the visitor no longer wants to visit anything.
+ */
+ IPath getRoot();
+
+ /**
+ * Returns whether the corresponding resource is affected by this change.
+ */
+ boolean isAffected(ResourceInfo info, IPathRequestor requestor);
+ }
+
+ private ThreadLocal disabled = new ThreadLocal();
+
+ private final Bundle systemBundle = Platform.getBundle("org.eclipse.osgi"); //$NON-NLS-1$
+ private Queue work = new Queue();
+
+ Workspace workspace;
+
+ private static final int CHARSET_DELTA_DELAY = 500;
+
+ public CharsetDeltaJob(Workspace workspace) {
+ super(Messages.resources_charsetBroadcasting);
+ this.workspace = workspace;
+ }
+
+ private void addToQueue(ICharsetListenerFilter filter) {
+ synchronized (work) {
+ work.add(filter);
+ }
+ schedule(CHARSET_DELTA_DELAY);
+ }
+
+ @Override
+ public boolean belongsTo(Object family) {
+ return FAMILY_CHARSET_DELTA.equals(family);
+ }
+
+ public void charsetPreferencesChanged(final IProject project) {
+ // avoid reacting to changes made by ourselves
+ if (isDisabled())
+ return;
+ ResourceInfo projectInfo = ((Project) project).getResourceInfo(false, false);
+ //nothing to do if project has already been deleted
+ if (projectInfo == null)
+ return;
+ final long projectId = projectInfo.getNodeId();
+ // ensure all resources under the affected project are
+ // reported as having encoding changes
+ ICharsetListenerFilter filter = new ICharsetListenerFilter() {
+ @Override
+ public IPath getRoot() {
+ //make sure it is still the same project - it could have been deleted and recreated
+ ResourceInfo currentInfo = ((Project) project).getResourceInfo(false, false);
+ if (currentInfo == null)
+ return null;
+ long currentId = currentInfo.getNodeId();
+ if (currentId != projectId)
+ return null;
+ // visit the project subtree
+ return project.getFullPath();
+ }
+
+ @Override
+ public boolean isAffected(ResourceInfo info, IPathRequestor requestor) {
+ // for now, mark all resources in the project as potential encoding resource changes
+ return true;
+ }
+ };
+ addToQueue(filter);
+ }
+
+ @Override
+ public void contentTypeChanged(final ContentTypeChangeEvent event) {
+ // check all files that may be affected by this change (taking
+ // only the current content type state into account
+ // dispatch a job to generate the deltas
+ ICharsetListenerFilter filter = new ICharsetListenerFilter() {
+
+ @Override
+ public IPath getRoot() {
+ // visit all resources in the workspace
+ return Path.ROOT;
+ }
+
+ @Override
+ public boolean isAffected(ResourceInfo info, IPathRequestor requestor) {
+ if (info.getType() != IResource.FILE)
+ return false;
+ return event.getContentType().isAssociatedWith(requestor.requestName());
+ }
+ };
+ addToQueue(filter);
+ }
+
+ private boolean isDisabled() {
+ return disabled.get() != null;
+ }
+
+ private void processNextEvent(final ICharsetListenerFilter filter, IProgressMonitor monitor) throws CoreException {
+ IElementContentVisitor visitor = new IElementContentVisitor() {
+ @Override
+ public boolean visitElement(ElementTree tree, IPathRequestor requestor, Object elementContents) {
+ ResourceInfo info = (ResourceInfo) elementContents;
+ if (!filter.isAffected(info, requestor))
+ return true;
+ info = workspace.getResourceInfo(requestor.requestPath(), false, true);
+ if (info == null)
+ return false;
+ info.incrementCharsetGenerationCount();
+ return true;
+ }
+ };
+ try {
+ IPath root = filter.getRoot();
+ if (root != null)
+ new ElementTreeIterator(workspace.getElementTree(), root).iterate(visitor);
+ } catch (WrappedRuntimeException e) {
+ throw (CoreException) e.getTargetException();
+ }
+ if (monitor.isCanceled())
+ throw new OperationCanceledException();
+ }
+
+ private ICharsetListenerFilter removeFromQueue() {
+ synchronized (work) {
+ return work.remove();
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.internal.jobs.InternalJob#run(org.eclipse.core.runtime.IProgressMonitor)
+ */
+ @Override
+ public IStatus run(IProgressMonitor monitor) {
+ monitor = Policy.monitorFor(monitor);
+ try {
+ String message = Messages.resources_charsetBroadcasting;
+ monitor.beginTask(message, Policy.totalWork);
+ try {
+ workspace.prepareOperation(null, monitor);
+ workspace.beginOperation(true);
+ ICharsetListenerFilter next;
+ //if the system is shutting down, don't broadcast
+ while (systemBundle.getState() != Bundle.STOPPING && (next = removeFromQueue()) != null)
+ processNextEvent(next, monitor);
+ } catch (OperationCanceledException e) {
+ workspace.getWorkManager().operationCanceled();
+ return Status.CANCEL_STATUS;
+ } finally {
+ workspace.endOperation(null, true, Policy.subMonitorFor(monitor, Policy.endOpWork));
+ }
+ monitor.worked(Policy.opWork);
+ } catch (CoreException sig) {
+ return sig.getStatus();
+ } finally {
+ monitor.done();
+ }
+ return Status.OK_STATUS;
+ }
+
+ /**
+ * Turns off reaction to changes in the preference file.
+ */
+ public void setDisabled(boolean disabled) {
+ // using a thread local because this can be called by multiple threads concurrently
+ this.disabled.set(disabled ? Boolean.TRUE : null);
+ }
+
+ public void shutdown() {
+ IContentTypeManager contentTypeManager = Platform.getContentTypeManager();
+ //if the service is already gone there is nothing to do
+ if (contentTypeManager != null)
+ contentTypeManager.removeContentTypeChangeListener(this);
+ }
+
+ public void startup() {
+ Platform.getContentTypeManager().addContentTypeChangeListener(this);
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/CharsetManager.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/CharsetManager.java
new file mode 100644
index 0000000000..7b25da5b07
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/CharsetManager.java
@@ -0,0 +1,507 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2015 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * James Blackburn (Broadcom Corp.) - ongoing development
+ * Tom Hochstein (Freescale) - Bug 409996 - 'Restore Defaults' does not work properly on Project Properties > Resource tab
+ *******************************************************************************/
+package org.eclipse.core.internal.resources;
+
+import java.util.*;
+import org.eclipse.core.internal.utils.Messages;
+import org.eclipse.core.internal.utils.Policy;
+import org.eclipse.core.resources.*;
+import org.eclipse.core.runtime.*;
+import org.eclipse.core.runtime.jobs.ISchedulingRule;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.core.runtime.preferences.IEclipsePreferences;
+import org.eclipse.core.runtime.preferences.InstanceScope;
+import org.osgi.framework.Bundle;
+import org.osgi.service.prefs.BackingStoreException;
+import org.osgi.service.prefs.Preferences;
+
+/**
+ * Manages user-defined encodings as preferences in the project content area.
+ *
+ * @since 3.0
+ */
+public class CharsetManager implements IManager {
+ /**
+ * This job implementation is used to allow the resource change listener
+ * to schedule operations that need to modify the workspace.
+ */
+ private class CharsetManagerJob extends Job {
+ private static final int CHARSET_UPDATE_DELAY = 500;
+ private List> asyncChanges = new ArrayList>();
+
+ public CharsetManagerJob() {
+ super(Messages.resources_charsetUpdating);
+ setSystem(true);
+ setPriority(Job.INTERACTIVE);
+ }
+
+ public void addChanges(Map newChanges) {
+ if (newChanges.isEmpty())
+ return;
+ synchronized (asyncChanges) {
+ asyncChanges.addAll(newChanges.entrySet());
+ asyncChanges.notify();
+ }
+ schedule(CHARSET_UPDATE_DELAY);
+ }
+
+ public Map.Entry getNextChange() {
+ synchronized (asyncChanges) {
+ return asyncChanges.isEmpty() ? null : asyncChanges.remove(asyncChanges.size() - 1);
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.internal.jobs.InternalJob#run(org.eclipse.core.runtime.IProgressMonitor)
+ */
+ @Override
+ protected IStatus run(IProgressMonitor monitor) {
+ MultiStatus result = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.FAILED_SETTING_CHARSET, Messages.resources_updatingEncoding, null);
+ monitor = Policy.monitorFor(monitor);
+ try {
+ monitor.beginTask(Messages.resources_charsetUpdating, Policy.totalWork);
+ final ISchedulingRule rule = workspace.getRuleFactory().modifyRule(workspace.getRoot());
+ try {
+ workspace.prepareOperation(rule, monitor);
+ workspace.beginOperation(true);
+ Map.Entry next;
+ while ((next = getNextChange()) != null) {
+ //just exit if the system is shutting down or has been shut down
+ //it is too late to change the workspace at this point anyway
+ if (systemBundle.getState() != Bundle.ACTIVE)
+ return Status.OK_STATUS;
+ IProject project = next.getKey();
+ try {
+ if (project.isAccessible()) {
+ boolean shouldDisableCharsetDeltaJob = next.getValue().booleanValue();
+ // flush preferences for non-derived resources
+ flushPreferences(getPreferences(project, false, false, true), shouldDisableCharsetDeltaJob);
+ // flush preferences for derived resources
+ flushPreferences(getPreferences(project, false, true, true), shouldDisableCharsetDeltaJob);
+ }
+ } catch (BackingStoreException e) {
+ // we got an error saving
+ String detailMessage = Messages.resources_savingEncoding;
+ result.add(new ResourceStatus(IResourceStatus.FAILED_SETTING_CHARSET, project.getFullPath(), detailMessage, e));
+ }
+ }
+ monitor.worked(Policy.opWork);
+ } catch (OperationCanceledException e) {
+ workspace.getWorkManager().operationCanceled();
+ throw e;
+ } finally {
+ workspace.endOperation(rule, true, Policy.subMonitorFor(monitor, Policy.endOpWork));
+ }
+ } catch (CoreException ce) {
+ return ce.getStatus();
+ } finally {
+ monitor.done();
+ }
+ return result;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.runtime.jobs.Job#shouldRun()
+ */
+ @Override
+ public boolean shouldRun() {
+ synchronized (asyncChanges) {
+ return !asyncChanges.isEmpty();
+ }
+ }
+ }
+
+ private class ResourceChangeListener implements IResourceChangeListener {
+ public ResourceChangeListener() {
+ }
+
+ private boolean moveSettingsIfDerivedChanged(IResourceDelta parent, IProject currentProject, Preferences projectPrefs, String[] affectedResources) {
+ boolean resourceChanges = false;
+
+ if ((parent.getFlags() & IResourceDelta.DERIVED_CHANGED) != 0) {
+ // if derived changed, move encoding to correct preferences
+ IPath parentPath = parent.getResource().getProjectRelativePath();
+ for (int i = 0; i < affectedResources.length; i++) {
+ IPath affectedPath = new Path(affectedResources[i]);
+ // if parentPath is an ancestor of affectedPath
+ if (parentPath.isPrefixOf(affectedPath)) {
+ IResource member = currentProject.findMember(affectedPath);
+ if (member != null) {
+ Preferences targetPrefs = getPreferences(currentProject, true, member.isDerived(IResource.CHECK_ANCESTORS));
+ // if new preferences are different than current
+ if (!projectPrefs.absolutePath().equals(targetPrefs.absolutePath())) {
+ // remove encoding from old preferences and save in correct preferences
+ String currentValue = projectPrefs.get(affectedResources[i], null);
+ projectPrefs.remove(affectedResources[i]);
+ targetPrefs.put(affectedResources[i], currentValue);
+ resourceChanges = true;
+ }
+ }
+ }
+ }
+ }
+
+ IResourceDelta[] children = parent.getAffectedChildren();
+ for (int i = 0; i < children.length; i++) {
+ resourceChanges = moveSettingsIfDerivedChanged(children[i], currentProject, projectPrefs, affectedResources) || resourceChanges;
+ }
+ return resourceChanges;
+ }
+
+ private void processEntryChanges(IResourceDelta projectDelta, Map projectsToSave) {
+ // check each resource with user-set encoding to see if it has
+ // been moved/deleted or if derived state has been changed
+ IProject currentProject = (IProject) projectDelta.getResource();
+ Preferences projectRegularPrefs = getPreferences(currentProject, false, false, true);
+ Preferences projectDerivedPrefs = getPreferences(currentProject, false, true, true);
+ Map affectedResourcesMap = new HashMap();
+ try {
+ // no regular preferences for this project
+ if (projectRegularPrefs == null)
+ affectedResourcesMap.put(Boolean.FALSE, new String[0]);
+ else
+ affectedResourcesMap.put(Boolean.FALSE, projectRegularPrefs.keys());
+ // no derived preferences for this project
+ if (projectDerivedPrefs == null)
+ affectedResourcesMap.put(Boolean.TRUE, new String[0]);
+ else
+ affectedResourcesMap.put(Boolean.TRUE, projectDerivedPrefs.keys());
+ } catch (BackingStoreException e) {
+ // problems with the project scope... we will miss the changes (but will log)
+ String message = Messages.resources_readingEncoding;
+ Policy.log(new ResourceStatus(IResourceStatus.FAILED_GETTING_CHARSET, currentProject.getFullPath(), message, e));
+ return;
+ }
+ for (Iterator it = affectedResourcesMap.keySet().iterator(); it.hasNext();) {
+ Boolean isDerived = it.next();
+ String[] affectedResources = affectedResourcesMap.get(isDerived);
+ Preferences projectPrefs = isDerived.booleanValue() ? projectDerivedPrefs : projectRegularPrefs;
+ for (int i = 0; i < affectedResources.length; i++) {
+ IResourceDelta memberDelta = projectDelta.findMember(new Path(affectedResources[i]));
+ // no changes for the given resource
+ if (memberDelta == null)
+ continue;
+ if (memberDelta.getKind() == IResourceDelta.REMOVED) {
+ boolean shouldDisableCharsetDeltaJobForCurrentProject = false;
+ // remove the setting for the original location - save its value though
+ String currentValue = projectPrefs.get(affectedResources[i], null);
+ projectPrefs.remove(affectedResources[i]);
+ if ((memberDelta.getFlags() & IResourceDelta.MOVED_TO) != 0) {
+ IPath movedToPath = memberDelta.getMovedToPath();
+ IResource resource = workspace.getRoot().findMember(movedToPath);
+ if (resource != null) {
+ Preferences encodingSettings = getPreferences(resource.getProject(), true, resource.isDerived(IResource.CHECK_ANCESTORS));
+ if (currentValue == null || currentValue.trim().length() == 0)
+ encodingSettings.remove(getKeyFor(movedToPath));
+ else
+ encodingSettings.put(getKeyFor(movedToPath), currentValue);
+ IProject targetProject = workspace.getRoot().getProject(movedToPath.segment(0));
+ if (targetProject.equals(currentProject))
+ // if the file was moved inside the same project disable charset listener
+ shouldDisableCharsetDeltaJobForCurrentProject = true;
+ else
+ projectsToSave.put(targetProject, Boolean.FALSE);
+ }
+ }
+ projectsToSave.put(currentProject, Boolean.valueOf(shouldDisableCharsetDeltaJobForCurrentProject));
+ }
+ }
+ if (moveSettingsIfDerivedChanged(projectDelta, currentProject, projectPrefs, affectedResources)) {
+ // if settings were moved between preferences files disable charset listener so we don't react to changes made by ourselves
+ projectsToSave.put(currentProject, Boolean.TRUE);
+ }
+ }
+ }
+
+ /**
+ * For any change to the encoding file or any resource with encoding
+ * set, just discard the cache for the corresponding project.
+ */
+ @Override
+ public void resourceChanged(IResourceChangeEvent event) {
+ IResourceDelta delta = event.getDelta();
+ if (delta == null)
+ return;
+ IResourceDelta[] projectDeltas = delta.getAffectedChildren();
+ // process each project in the delta
+ Map projectsToSave = new HashMap();
+ for (int i = 0; i < projectDeltas.length; i++)
+ //nothing to do if a project has been added/removed/moved
+ if (projectDeltas[i].getKind() == IResourceDelta.CHANGED && (projectDeltas[i].getFlags() & IResourceDelta.OPEN) == 0)
+ processEntryChanges(projectDeltas[i], projectsToSave);
+ job.addChanges(projectsToSave);
+ }
+ }
+
+ private static final String PROJECT_KEY = ""; //$NON-NLS-1$
+ private CharsetDeltaJob charsetListener;
+ CharsetManagerJob job;
+ private IResourceChangeListener resourceChangeListener;
+ protected final Bundle systemBundle = Platform.getBundle("org.eclipse.osgi"); //$NON-NLS-1$
+ Workspace workspace;
+
+ public CharsetManager(Workspace workspace) {
+ this.workspace = workspace;
+ }
+
+ void flushPreferences(Preferences projectPrefs, boolean shouldDisableCharsetDeltaJob) throws BackingStoreException {
+ if (projectPrefs != null) {
+ try {
+ if (shouldDisableCharsetDeltaJob)
+ charsetListener.setDisabled(true);
+ projectPrefs.flush();
+ } finally {
+ if (shouldDisableCharsetDeltaJob)
+ charsetListener.setDisabled(false);
+ }
+ }
+ }
+
+ /**
+ * Returns the charset explicitly set by the user for the given resource,
+ * or null. If no setting exists for the given resource and
+ * recurse is true, every parent up to the
+ * workspace root will be checked until a charset setting can be found.
+ *
+ * @param resourcePath the path for the resource
+ * @param recurse whether the parent should be queried
+ * @return the charset setting for the given resource
+ */
+ public String getCharsetFor(IPath resourcePath, boolean recurse) {
+ Assert.isLegal(resourcePath.segmentCount() >= 1);
+ IProject project = workspace.getRoot().getProject(resourcePath.segment(0));
+
+ Preferences prefs = getPreferences(project, false, false);
+ Preferences derivedPrefs = getPreferences(project, false, true);
+
+ if (prefs == null && derivedPrefs == null)
+ // no preferences found - for performance reasons, short-circuit
+ // lookup by falling back to workspace's default setting
+ return recurse ? ResourcesPlugin.getEncoding() : null;
+
+ return internalGetCharsetFor(prefs, derivedPrefs, resourcePath, recurse);
+ }
+
+ static String getKeyFor(IPath resourcePath) {
+ return resourcePath.segmentCount() > 1 ? resourcePath.removeFirstSegments(1).toString() : PROJECT_KEY;
+ }
+
+ Preferences getPreferences(IProject project, boolean create, boolean isDerived) {
+ return getPreferences(project, create, isDerived, isDerivedEncodingStoredSeparately(project));
+ }
+
+ Preferences getPreferences(IProject project, boolean create, boolean isDerived, boolean isDerivedEncodingStoredSeparately) {
+ boolean localIsDerived = isDerivedEncodingStoredSeparately ? isDerived : false;
+ String qualifier = localIsDerived ? ProjectPreferences.PREFS_DERIVED_QUALIFIER : ProjectPreferences.PREFS_REGULAR_QUALIFIER;
+ if (create)
+ // create all nodes down to the one we are interested in
+ return new ProjectScope(project).getNode(qualifier).node(ResourcesPlugin.PREF_ENCODING);
+ // be careful looking up for our node so not to create any nodes as side effect
+ Preferences node = Platform.getPreferencesService().getRootNode().node(ProjectScope.SCOPE);
+ try {
+ //TODO once bug 90500 is fixed, should be as simple as this:
+ // String path = project.getName() + IPath.SEPARATOR + ResourcesPlugin.PI_RESOURCES + IPath.SEPARATOR + ENCODING_PREF_NODE;
+ // return node.nodeExists(path) ? node.node(path) : null;
+ // for now, take the long way
+ if (!node.nodeExists(project.getName()))
+ return null;
+ node = node.node(project.getName());
+ if (!node.nodeExists(qualifier))
+ return null;
+ node = node.node(qualifier);
+ if (!node.nodeExists(ResourcesPlugin.PREF_ENCODING))
+ return null;
+ return node.node(ResourcesPlugin.PREF_ENCODING);
+ } catch (BackingStoreException e) {
+ // nodeExists failed
+ String message = Messages.resources_readingEncoding;
+ Policy.log(new ResourceStatus(IResourceStatus.FAILED_GETTING_CHARSET, project.getFullPath(), message, e));
+ }
+ return null;
+ }
+
+ private String internalGetCharsetFor(Preferences prefs, Preferences derivedPrefs, IPath resourcePath, boolean recurse) {
+ String charset = null;
+
+ // try to find the encoding in regular and then derived preferences
+ if (prefs != null)
+ charset = prefs.get(getKeyFor(resourcePath), null);
+ // derivedPrefs may be not null, only if #isDerivedEncodingStoredSeparately returns true
+ // so the explicit check against #isDerivedEncodingStoredSeparately is not required
+ if (charset == null && derivedPrefs != null)
+ charset = derivedPrefs.get(getKeyFor(resourcePath), null);
+
+ if (!recurse)
+ return charset;
+
+ while (charset == null && resourcePath.segmentCount() > 1) {
+ resourcePath = resourcePath.removeLastSegments(1);
+ // try to find the encoding in regular and then derived preferences
+ if (prefs != null)
+ charset = prefs.get(getKeyFor(resourcePath), null);
+ if (charset == null && derivedPrefs != null)
+ charset = derivedPrefs.get(getKeyFor(resourcePath), null);
+ }
+
+ // ensure we default to the workspace encoding if none is found
+ return charset == null ? ResourcesPlugin.getEncoding() : charset;
+ }
+
+ private boolean isDerivedEncodingStoredSeparately(IProject project) {
+ // be careful looking up for our node so not to create any nodes as side effect
+ Preferences node = Platform.getPreferencesService().getRootNode().node(ProjectScope.SCOPE);
+ try {
+ //TODO once bug 90500 is fixed, should be as simple as this:
+ // String path = project.getName() + IPath.SEPARATOR + ResourcesPlugin.PI_RESOURCES;
+ // return node.nodeExists(path) ? node.node(path).getBoolean(ResourcesPlugin.PREF_SEPARATE_DERIVED_ENCODINGS, false) : false;
+ // for now, take the long way
+ if (!node.nodeExists(project.getName()))
+ return ResourcesPlugin.DEFAULT_PREF_SEPARATE_DERIVED_ENCODINGS;
+ node = node.node(project.getName());
+ if (!node.nodeExists(ResourcesPlugin.PI_RESOURCES))
+ return ResourcesPlugin.DEFAULT_PREF_SEPARATE_DERIVED_ENCODINGS;
+ node = node.node(ResourcesPlugin.PI_RESOURCES);
+ return node.getBoolean(ResourcesPlugin.PREF_SEPARATE_DERIVED_ENCODINGS, ResourcesPlugin.DEFAULT_PREF_SEPARATE_DERIVED_ENCODINGS);
+ } catch (BackingStoreException e) {
+ // nodeExists failed
+ String message = Messages.resources_readingEncoding;
+ Policy.log(new ResourceStatus(IResourceStatus.FAILED_GETTING_CHARSET, project.getFullPath(), message, e));
+ return ResourcesPlugin.DEFAULT_PREF_SEPARATE_DERIVED_ENCODINGS;
+ }
+ }
+
+ protected void mergeEncodingPreferences(IProject project) {
+ Preferences projectRegularPrefs = null;
+ Preferences projectDerivedPrefs = getPreferences(project, false, true, true);
+ if (projectDerivedPrefs == null)
+ return;
+ try {
+ boolean prefsChanged = false;
+ String[] affectedResources;
+ affectedResources = projectDerivedPrefs.keys();
+ for (int i = 0; i < affectedResources.length; i++) {
+ String path = affectedResources[i];
+ String value = projectDerivedPrefs.get(path, null);
+ projectDerivedPrefs.remove(path);
+ // lazy creation of non-derived preferences
+ if (projectRegularPrefs == null)
+ projectRegularPrefs = getPreferences(project, true, false, false);
+ projectRegularPrefs.put(path, value);
+ prefsChanged = true;
+ }
+ if (prefsChanged) {
+ Map projectsToSave = new HashMap();
+ // this is internal change so do not notify charset delta job
+ projectsToSave.put(project, Boolean.TRUE);
+ job.addChanges(projectsToSave);
+ }
+ } catch (BackingStoreException e) {
+ // problems with the project scope... we will miss the changes (but will log)
+ String message = Messages.resources_readingEncoding;
+ Policy.log(new ResourceStatus(IResourceStatus.FAILED_GETTING_CHARSET, project.getFullPath(), message, e));
+ }
+ }
+
+ public void projectPreferencesChanged(IProject project) {
+ charsetListener.charsetPreferencesChanged(project);
+ }
+
+ public void setCharsetFor(IPath resourcePath, String newCharset) throws CoreException {
+ // for the workspace root we just set a preference in the instance scope
+ if (resourcePath.segmentCount() == 0) {
+ IEclipsePreferences resourcesPreferences = InstanceScope.INSTANCE.getNode(ResourcesPlugin.PI_RESOURCES);
+ if (newCharset != null)
+ resourcesPreferences.put(ResourcesPlugin.PREF_ENCODING, newCharset);
+ else
+ resourcesPreferences.remove(ResourcesPlugin.PREF_ENCODING);
+ try {
+ resourcesPreferences.flush();
+ } catch (BackingStoreException e) {
+ IProject project = workspace.getRoot().getProject(resourcePath.segment(0));
+ String message = Messages.resources_savingEncoding;
+ throw new ResourceException(IResourceStatus.FAILED_SETTING_CHARSET, project.getFullPath(), message, e);
+ }
+ return;
+ }
+ // for all other cases, we set a property in the corresponding project
+ IResource resource = workspace.getRoot().findMember(resourcePath);
+ if (resource != null) {
+ try {
+ // disable the listener so we don't react to changes made by ourselves
+ Preferences encodingSettings = getPreferences(resource.getProject(), true, resource.isDerived(IResource.CHECK_ANCESTORS));
+ if (newCharset == null || newCharset.trim().length() == 0)
+ encodingSettings.remove(getKeyFor(resourcePath));
+ else
+ encodingSettings.put(getKeyFor(resourcePath), newCharset);
+ flushPreferences(encodingSettings, true);
+ } catch (BackingStoreException e) {
+ IProject project = workspace.getRoot().getProject(resourcePath.segment(0));
+ String message = Messages.resources_savingEncoding;
+ throw new ResourceException(IResourceStatus.FAILED_SETTING_CHARSET, project.getFullPath(), message, e);
+ }
+ }
+ }
+
+ @Override
+ public void shutdown(IProgressMonitor monitor) {
+ workspace.removeResourceChangeListener(resourceChangeListener);
+ if (charsetListener != null)
+ charsetListener.shutdown();
+ }
+
+ protected void splitEncodingPreferences(IProject project) {
+ Preferences projectRegularPrefs = getPreferences(project, false, false, false);
+ Preferences projectDerivedPrefs = null;
+ if (projectRegularPrefs == null)
+ return;
+ try {
+ boolean prefsChanged = false;
+ String[] affectedResources;
+ affectedResources = projectRegularPrefs.keys();
+ for (int i = 0; i < affectedResources.length; i++) {
+ String path = affectedResources[i];
+ IResource resource = project.findMember(path);
+ if (resource != null) {
+ if (resource.isDerived(IResource.CHECK_ANCESTORS)) {
+ String value = projectRegularPrefs.get(path, null);
+ projectRegularPrefs.remove(path);
+ // lazy creation of derived preferences
+ if (projectDerivedPrefs == null)
+ projectDerivedPrefs = getPreferences(project, true, true, true);
+ projectDerivedPrefs.put(path, value);
+ prefsChanged = true;
+ }
+ }
+ }
+ if (prefsChanged) {
+ Map projectsToSave = new HashMap();
+ // this is internal change so do not notify charset delta job
+ projectsToSave.put(project, Boolean.TRUE);
+ job.addChanges(projectsToSave);
+ }
+ } catch (BackingStoreException e) {
+ // problems with the project scope... we will miss the changes (but will log)
+ String message = Messages.resources_readingEncoding;
+ Policy.log(new ResourceStatus(IResourceStatus.FAILED_GETTING_CHARSET, project.getFullPath(), message, e));
+ }
+ }
+
+ @Override
+ public void startup(IProgressMonitor monitor) {
+ job = new CharsetManagerJob();
+ resourceChangeListener = new ResourceChangeListener();
+ workspace.addResourceChangeListener(resourceChangeListener, IResourceChangeEvent.POST_CHANGE);
+ charsetListener = new CharsetDeltaJob(workspace);
+ charsetListener.startup();
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ComputeProjectOrder.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ComputeProjectOrder.java
new file mode 100644
index 0000000000..a4921ee909
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ComputeProjectOrder.java
@@ -0,0 +1,620 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * Broadcom Corporation - ongoing development
+ *******************************************************************************/
+
+package org.eclipse.core.internal.resources;
+
+import java.util.*;
+
+/**
+ * Implementation of a sort algorithm for computing the order of vertexes that are part
+ * of a reference graph. This algorithm handles cycles in the graph in a reasonable way.
+ * In 3.7 this class was enhanced to support computing order of a graph containing an
+ * arbitrary type.
+ *
+ * @since 2.1
+ */
+class ComputeProjectOrder {
+
+ /*
+ * Prevent class from being instantiated.
+ */
+ private ComputeProjectOrder() {
+ // not allowed
+ }
+
+ /**
+ * A directed graph. Once the vertexes and edges of the graph have been
+ * defined, the graph can be queried for the depth-first finish time of each
+ * vertex.
+ *
+ * Ref: Cormen, Leiserson, and Rivest Introduction to Algorithms,
+ * McGraw-Hill, 1990. The depth-first search algorithm is in section 23.3.
+ *
+ */
+ private static class Digraph {
+ /**
+ * struct-like object for representing a vertex along with various
+ * values computed during depth-first search (DFS).
+ */
+ public static class Vertex {
+ /**
+ * White is for marking vertexes as unvisited.
+ */
+ public static final String WHITE = "white"; //$NON-NLS-1$
+
+ /**
+ * Grey is for marking vertexes as discovered but visit not yet
+ * finished.
+ */
+ public static final String GREY = "grey"; //$NON-NLS-1$
+
+ /**
+ * Black is for marking vertexes as visited.
+ */
+ public static final String BLACK = "black"; //$NON-NLS-1$
+
+ /**
+ * Color of the vertex. One of WHITE (unvisited),
+ * GREY (visit in progress), or BLACK
+ * (visit finished). WHITE initially.
+ */
+ public String color = WHITE;
+
+ /**
+ * The DFS predecessor vertex, or null if there is no
+ * predecessor. null initially.
+ */
+ public Vertex predecessor = null;
+
+ /**
+ * Timestamp indicating when the vertex was finished (became BLACK)
+ * in the DFS. Finish times are between 1 and the number of
+ * vertexes.
+ */
+ public int finishTime;
+
+ /**
+ * The id of this vertex.
+ */
+ public Object id;
+
+ /**
+ * Ordered list of adjacent vertexes. In other words, "this" is the
+ * "from" vertex and the elements of this list are all "to"
+ * vertexes.
+ *
+ * Element type: Vertex
+ */
+ public List adjacent = new ArrayList(3);
+
+ /**
+ * Creates a new vertex with the given id.
+ *
+ * @param id the vertex id
+ */
+ public Vertex(Object id) {
+ this.id = id;
+ }
+ }
+
+ /**
+ * Ordered list of all vertexes in this graph.
+ *
+ * Element type: Vertex
+ */
+ private List vertexList = new ArrayList(100);
+
+ /**
+ * Map from id to vertex.
+ *
+ * Key type: Object; value type: Vertex
+ */
+ private Map vertexMap = new HashMap(100);
+
+ /**
+ * DFS visit time. Non-negative.
+ */
+ private int time;
+
+ /**
+ * Indicates whether the graph has been initialized. Initially
+ * false.
+ */
+ private boolean initialized = false;
+
+ /**
+ * Indicates whether the graph contains cycles. Initially
+ * false.
+ */
+ private boolean cycles = false;
+
+ /**
+ * Creates a new empty directed graph object.
+ *
+ * After this graph's vertexes and edges are defined with
+ * addVertex and addEdge, call
+ * freeze to indicate that the graph is all there, and then
+ * call idsByDFSFinishTime to read off the vertexes ordered
+ * by DFS finish time.
+ *
+ */
+ public Digraph() {
+ super();
+ }
+
+ /**
+ * Freezes this graph. No more vertexes or edges can be added to this
+ * graph after this method is called. Has no effect if the graph is
+ * already frozen.
+ */
+ public void freeze() {
+ if (!initialized) {
+ initialized = true;
+ // only perform depth-first-search once
+ DFS();
+ }
+ }
+
+ /**
+ * Defines a new vertex with the given id. The depth-first search is
+ * performed in the relative order in which vertexes were added to the
+ * graph.
+ *
+ * @param id the id of the vertex
+ * @exception IllegalArgumentException if the vertex id is
+ * already defined or if the graph is frozen
+ */
+ public void addVertex(Object id) throws IllegalArgumentException {
+ if (initialized) {
+ throw new IllegalArgumentException();
+ }
+ Vertex vertex = new Vertex(id);
+ Object existing = vertexMap.put(id, vertex);
+ // nip problems with duplicate vertexes in the bud
+ if (existing != null) {
+ throw new IllegalArgumentException();
+ }
+ vertexList.add(vertex);
+ }
+
+ /**
+ * Adds a new directed edge between the vertexes with the given ids.
+ * Vertexes for the given ids must be defined beforehand with
+ * addVertex. The depth-first search is performed in the
+ * relative order in which adjacent "to" vertexes were added to a given
+ * "from" index.
+ *
+ * @param fromId the id of the "from" vertex
+ * @param toId the id of the "to" vertex
+ * @exception IllegalArgumentException if either vertex is undefined or
+ * if the graph is frozen
+ */
+ public void addEdge(Object fromId, Object toId) throws IllegalArgumentException {
+ if (initialized) {
+ throw new IllegalArgumentException();
+ }
+ Vertex fromVertex = vertexMap.get(fromId);
+ Vertex toVertex = vertexMap.get(toId);
+ // nip problems with bogus vertexes in the bud
+ if (fromVertex == null) {
+ throw new IllegalArgumentException();
+ }
+ if (toVertex == null) {
+ throw new IllegalArgumentException();
+ }
+ fromVertex.adjacent.add(toVertex);
+ }
+
+ /**
+ * Returns the ids of the vertexes in this graph ordered by depth-first
+ * search finish time. The graph must be frozen.
+ *
+ * @param increasing true if objects are to be arranged
+ * into increasing order of depth-first search finish time, and
+ * false if objects are to be arranged into decreasing
+ * order of depth-first search finish time
+ * @return the list of ids ordered by depth-first search finish time
+ * (element type: Object)
+ * @exception IllegalArgumentException if the graph is not frozen
+ */
+ public List idsByDFSFinishTime(boolean increasing) {
+ if (!initialized) {
+ throw new IllegalArgumentException();
+ }
+ int len = vertexList.size();
+ Object[] r = new Object[len];
+ for (Iterator allV = vertexList.iterator(); allV.hasNext();) {
+ Vertex vertex = allV.next();
+ int f = vertex.finishTime;
+ // note that finish times start at 1, not 0
+ if (increasing) {
+ r[f - 1] = vertex.id;
+ } else {
+ r[len - f] = vertex.id;
+ }
+ }
+ return Arrays.asList(r);
+ }
+
+ /**
+ * Returns whether the graph contains cycles. The graph must be frozen.
+ *
+ * @return true if this graph contains at least one cycle,
+ * and false if this graph is cycle free
+ * @exception IllegalArgumentException if the graph is not frozen
+ */
+ public boolean containsCycles() {
+ if (!initialized) {
+ throw new IllegalArgumentException();
+ }
+ return cycles;
+ }
+
+ /**
+ * Returns the non-trivial components of this graph. A non-trivial
+ * component is a set of 2 or more vertexes that were traversed
+ * together. The graph must be frozen.
+ *
+ * @return the possibly empty list of non-trivial components, where
+ * each component is an array of ids (element type:
+ * Object[])
+ * @exception IllegalArgumentException if the graph is not frozen
+ */
+ public List nonTrivialComponents() {
+ if (!initialized) {
+ throw new IllegalArgumentException();
+ }
+ // find the roots of each component
+ // Map> components
+ Map> components = new HashMap>();
+ for (Iterator it = vertexList.iterator(); it.hasNext();) {
+ Vertex vertex = it.next();
+ if (vertex.predecessor == null) {
+ // this vertex is the root of a component
+ // if component is non-trivial we will hit a child
+ } else {
+ // find the root ancestor of this vertex
+ Vertex root = vertex;
+ while (root.predecessor != null) {
+ root = root.predecessor;
+ }
+ List component = components.get(root);
+ if (component == null) {
+ component = new ArrayList(2);
+ component.add(root.id);
+ components.put(root, component);
+ }
+ component.add(vertex.id);
+ }
+ }
+ List result = new ArrayList(components.size());
+ for (Iterator> it = components.values().iterator(); it.hasNext();) {
+ List component = it.next();
+ if (component.size() > 1) {
+ result.add(component.toArray());
+ }
+ }
+ return result;
+ }
+
+ // /**
+ // * Performs a depth-first search of this graph and records interesting
+ // * info with each vertex, including DFS finish time. Employs a recursive
+ // * helper method DFSVisit.
+ // *
+ // * Although this method is not used, it is the basis of the
+ // * non-recursive DFS method.
+ // *
+ // */
+ // private void recursiveDFS() {
+ // // initialize
+ // // all vertex.color initially Vertex.WHITE;
+ // // all vertex.predecessor initially null;
+ // time = 0;
+ // for (Iterator allV = vertexList.iterator(); allV.hasNext();) {
+ // Vertex nextVertex = (Vertex) allV.next();
+ // if (nextVertex.color == Vertex.WHITE) {
+ // DFSVisit(nextVertex);
+ // }
+ // }
+ // }
+ //
+ // /**
+ // * Helper method. Performs a depth first search of this graph.
+ // *
+ // * @param vertex the vertex to visit
+ // */
+ // private void DFSVisit(Vertex vertex) {
+ // // mark vertex as discovered
+ // vertex.color = Vertex.GREY;
+ // List adj = vertex.adjacent;
+ // for (Iterator allAdjacent=adj.iterator(); allAdjacent.hasNext();) {
+ // Vertex adjVertex = (Vertex) allAdjacent.next();
+ // if (adjVertex.color == Vertex.WHITE) {
+ // // explore edge from vertex to adjVertex
+ // adjVertex.predecessor = vertex;
+ // DFSVisit(adjVertex);
+ // } else if (adjVertex.color == Vertex.GREY) {
+ // // back edge (grey vertex means visit in progress)
+ // cycles = true;
+ // }
+ // }
+ // // done exploring vertex
+ // vertex.color = Vertex.BLACK;
+ // time++;
+ // vertex.finishTime = time;
+ // }
+
+ /**
+ * Performs a depth-first search of this graph and records interesting
+ * info with each vertex, including DFS finish time. Does not employ
+ * recursion.
+ */
+ @SuppressWarnings({"unchecked"})
+ private void DFS() {
+ // state machine rendition of the standard recursive DFS algorithm
+ int state;
+ final int NEXT_VERTEX = 1;
+ final int START_DFS_VISIT = 2;
+ final int NEXT_ADJACENT = 3;
+ final int AFTER_NEXTED_DFS_VISIT = 4;
+ // use precomputed objects to avoid garbage
+ final Integer NEXT_VERTEX_OBJECT = new Integer(NEXT_VERTEX);
+ final Integer AFTER_NEXTED_DFS_VISIT_OBJECT = new Integer(AFTER_NEXTED_DFS_VISIT);
+ // initialize
+ // all vertex.color initially Vertex.WHITE;
+ // all vertex.predecessor initially null;
+ time = 0;
+ // for a stack, append to the end of an array-based list
+ List stack = new ArrayList(Math.max(1, vertexList.size()));
+ Iterator allAdjacent = null;
+ Vertex vertex = null;
+ Iterator allV = vertexList.iterator();
+ state = NEXT_VERTEX;
+ nextStateLoop: while (true) {
+ switch (state) {
+ case NEXT_VERTEX :
+ // on entry, "allV" contains vertexes yet to be visited
+ if (!allV.hasNext()) {
+ // all done
+ break nextStateLoop;
+ }
+ Vertex nextVertex = allV.next();
+ if (nextVertex.color == Vertex.WHITE) {
+ stack.add(NEXT_VERTEX_OBJECT);
+ vertex = nextVertex;
+ state = START_DFS_VISIT;
+ continue nextStateLoop;
+ }
+ //else
+ state = NEXT_VERTEX;
+ continue nextStateLoop;
+ case START_DFS_VISIT :
+ // on entry, "vertex" contains the vertex to be visited
+ // top of stack is return code
+ // mark the vertex as discovered
+ vertex.color = Vertex.GREY;
+ allAdjacent = vertex.adjacent.iterator();
+ state = NEXT_ADJACENT;
+ continue nextStateLoop;
+ case NEXT_ADJACENT :
+ // on entry, "allAdjacent" contains adjacent vertexes to
+ // be visited; "vertex" contains vertex being visited
+ if (allAdjacent.hasNext()) {
+ Vertex adjVertex = allAdjacent.next();
+ if (adjVertex.color == Vertex.WHITE) {
+ // explore edge from vertex to adjVertex
+ adjVertex.predecessor = vertex;
+ stack.add(allAdjacent);
+ stack.add(vertex);
+ stack.add(AFTER_NEXTED_DFS_VISIT_OBJECT);
+ vertex = adjVertex;
+ state = START_DFS_VISIT;
+ continue nextStateLoop;
+ }
+ if (adjVertex.color == Vertex.GREY) {
+ // back edge (grey means visit in progress)
+ cycles = true;
+ }
+ state = NEXT_ADJACENT;
+ continue nextStateLoop;
+ }
+ //else done exploring vertex
+ vertex.color = Vertex.BLACK;
+ time++;
+ vertex.finishTime = time;
+ state = ((Integer) stack.remove(stack.size() - 1)).intValue();
+ continue nextStateLoop;
+ case AFTER_NEXTED_DFS_VISIT :
+ // on entry, stack contains "vertex" and "allAjacent"
+ vertex = (Vertex) stack.remove(stack.size() - 1);
+ allAdjacent = (Iterator) stack.remove(stack.size() - 1);
+ state = NEXT_ADJACENT;
+ continue nextStateLoop;
+ }
+ }
+ }
+
+ }
+
+ /**
+ * Data structure for holding the multi-part outcome of
+ * ComputeVertexOrder.computeVertexOrder.
+ */
+ static final class VertexOrder {
+ /**
+ * Creates an instance with the given values.
+ * @param vertexes initial value of vertexes field
+ * @param hasCycles initial value of hasCycles field
+ * @param knots initial value of knots field
+ */
+ public VertexOrder(Object[] vertexes, boolean hasCycles, Object[][] knots) {
+ this.vertexes = vertexes;
+ this.hasCycles = hasCycles;
+ this.knots = knots;
+ }
+
+ /**
+ * A list of vertexes ordered so as to honor the reference
+ * relationships between them wherever possible.
+ */
+ public Object[] vertexes;
+ /**
+ * true if any of the vertexes in vertexes
+ * are involved in non-trivial cycles in the reference graph.
+ */
+ public boolean hasCycles;
+ /**
+ * A list of knots in the reference graph. This list is empty if
+ * the reference graph does not contain cycles. If the reference graph
+ * contains cycles, each element is a knot of two or more vertexes that
+ * are involved in a cycle of mutually dependent references.
+ */
+ public Object[][] knots;
+ }
+
+ /**
+ * Sorts the given list of vertexes in a manner that honors the given
+ * reference relationships between them. That is, if A references
+ * B, then the resulting order will list B before A if possible.
+ * For graphs that do not contain cycles, the result is the same as a conventional
+ * topological sort. For graphs containing cycles, the order is based on
+ * ordering the strongly connected components of the graph. This has the
+ * effect of keeping each knot of vertexes together without otherwise
+ * affecting the order of vertexes not involved in a cycle. For a graph G,
+ * the algorithm performs in O(|G|) space and time.
+ *
+ * When there is an arbitrary choice, vertexes are ordered as supplied.
+ * If there are no constraints on the order of the vertexes, they are returned
+ * in the reverse order of how they are supplied.
+ *
+ *
Ref: Cormen, Leiserson, and Rivest Introduction to
+ * Algorithms, McGraw-Hill, 1990. The strongly-connected-components
+ * algorithm is in section 23.5.
+ *
+ *
+ * @param vertexes a list of vertexes
+ * @param references a list of pairs [A,B] meaning that A references B
+ * @return an object describing the resulting order
+ */
+ static VertexOrder computeVertexOrder(SortedSet extends Object> vertexes, List extends Object[]> references) {
+
+ // Step 1: Create the graph object.
+ final Digraph g1 = new Digraph();
+ // add vertexes
+ for (Iterator> it = vertexes.iterator(); it.hasNext();) {
+ g1.addVertex(it.next());
+ }
+ // add edges
+ for (Iterator extends Object[]> it = references.iterator(); it.hasNext();) {
+ Object[] ref = it.next();
+ Object p = ref[0];
+ Object q = ref[1];
+ // p has a reference to q
+ // therefore create an edge from q to p
+ // to cause q to come before p in eventual result
+ g1.addEdge(q, p);
+ }
+ g1.freeze();
+
+ // Step 2: Create the transposed graph. This time, define the vertexes
+ // in decreasing order of depth-first finish time in g1
+ // interchange "to" and "from" to reverse edges from g1
+ final Digraph g2 = new Digraph();
+ // add vertexes
+ List resortedVertexes = g1.idsByDFSFinishTime(false);
+ for (Iterator it = resortedVertexes.iterator(); it.hasNext();) {
+ g2.addVertex(it.next());
+ }
+ // add edges
+ for (Iterator extends Object[]> it = references.iterator(); it.hasNext();) {
+ Object[] ref = it.next();
+ Object p = ref[0];
+ Object q = ref[1];
+ // p has a reference to q
+ // therefore create an edge from p to q
+ // N.B. this is the reverse of step 1
+ g2.addEdge(p, q);
+ }
+ g2.freeze();
+
+ // Step 3: Return the vertexes in increasing order of depth-first finish
+ // time in g2
+ List sortedVertexList = g2.idsByDFSFinishTime(true);
+ Object[] orderedVertexes = new Object[sortedVertexList.size()];
+ sortedVertexList.toArray(orderedVertexes);
+ Object[][] knots;
+ boolean hasCycles = g2.containsCycles();
+ if (hasCycles) {
+ List knotList = g2.nonTrivialComponents();
+ knots = new Object[knotList.size()][];
+ knotList.toArray(knots);
+ } else {
+ knots = new Object[][] {};
+ }
+ return new VertexOrder(orderedVertexes, hasCycles, knots);
+ }
+
+ static interface VertexFilter {
+ boolean matches(Object vertex);
+ }
+
+ /**
+ * Given a VertexOrder and a VertexFilter, remove all vertexes
+ * matching the filter from the ordering.
+ */
+ static VertexOrder filterVertexOrder(VertexOrder order, VertexFilter filter) {
+ // Optimize common case where nothing is to be filtered
+ // and cache the results of applying the filter
+ int filteredCount = 0;
+ boolean[] filterMatches = new boolean[order.vertexes.length];
+ for (int i = 0; i < order.vertexes.length; i++) {
+ filterMatches[i] = filter.matches(order.vertexes[i]);
+ if (filterMatches[i])
+ filteredCount++;
+ }
+
+ // No vertexes match the filter, so return the order unmodified
+ if (filteredCount == 0) {
+ return order;
+ }
+
+ // Otherwise we need to eliminate mention of vertexes matching the filter
+ // from the list of vertexes
+ Object[] reducedVertexes = new Object[order.vertexes.length - filteredCount];
+ for (int i = 0, j = 0; i < order.vertexes.length; i++) {
+ if (!filterMatches[i]) {
+ reducedVertexes[j] = order.vertexes[i];
+ j++;
+ }
+ }
+
+ // and from the knots list
+ List reducedKnots = new ArrayList(order.knots.length);
+ for (int i = 0; i < order.knots.length; i++) {
+ Object[] knot = order.knots[i];
+ List knotList = new ArrayList(knot.length);
+ for (int j = 0; j < knot.length; j++) {
+ Object vertex = knot[j];
+ if (!filter.matches(vertex)) {
+ knotList.add(vertex);
+ }
+ }
+ // Keep knots containing 2 or more vertexes in the specified subset
+ if (knotList.size() > 1) {
+ reducedKnots.add(knotList.toArray());
+ }
+ }
+
+ return new VertexOrder(reducedVertexes, reducedKnots.size() > 0, reducedKnots.toArray(new Object[reducedKnots.size()][]));
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Container.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Container.java
new file mode 100644
index 0000000000..195734f8e7
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Container.java
@@ -0,0 +1,386 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2015 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * James Blackburn (Broadcom Corp.) - ongoing development
+ *******************************************************************************/
+package org.eclipse.core.internal.resources;
+
+import java.util.*;
+import org.eclipse.core.internal.events.LifecycleEvent;
+import org.eclipse.core.internal.localstore.IHistoryStore;
+import org.eclipse.core.internal.utils.*;
+import org.eclipse.core.internal.watson.*;
+import org.eclipse.core.resources.*;
+import org.eclipse.core.runtime.*;
+import org.eclipse.core.runtime.jobs.ISchedulingRule;
+import org.eclipse.osgi.util.NLS;
+
+public abstract class Container extends Resource implements IContainer {
+ protected Container(IPath path, Workspace container) {
+ super(path, container);
+ }
+
+ /**
+ * Converts this resource and all its children into phantoms by modifying
+ * their resource infos in-place.
+ */
+ @Override
+ public void convertToPhantom() throws CoreException {
+ if (isPhantom())
+ return;
+ super.convertToPhantom();
+ IResource[] members = members(IContainer.INCLUDE_PHANTOMS | IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS | IContainer.INCLUDE_HIDDEN);
+ for (int i = 0; i < members.length; i++)
+ ((Resource) members[i]).convertToPhantom();
+ }
+
+ @Override
+ public IResourceFilterDescription createFilter(int type, FileInfoMatcherDescription matcherDescription, int updateFlags, IProgressMonitor monitor) throws CoreException {
+ Assert.isNotNull(getProject());
+ monitor = Policy.monitorFor(monitor);
+ FilterDescription filter = null;
+ try {
+ String message = NLS.bind(Messages.links_creating, getFullPath());
+ monitor.beginTask(message, Policy.totalWork);
+ Policy.checkCanceled(monitor);
+ checkValidPath(path, FOLDER | PROJECT, true);
+ final ISchedulingRule rule = workspace.getRuleFactory().createRule(this);
+ try {
+ workspace.prepareOperation(rule, monitor);
+ workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_FILTER_ADD, this));
+ workspace.beginOperation(true);
+ monitor.worked(Policy.opWork * 5 / 100);
+ //save the filter in the project description
+ filter = new FilterDescription(this, type, matcherDescription);
+ filter.setId(System.currentTimeMillis());
+
+ Project project = (Project) getProject();
+ project.internalGetDescription().addFilter(getProjectRelativePath(), filter);
+ project.writeDescription(IResource.NONE);
+ monitor.worked(Policy.opWork * 5 / 100);
+
+ //refresh to discover any new resources below this folder
+ if (getType() != IResource.FILE) {
+ //refresh either in background or foreground
+ if ((updateFlags & IResource.BACKGROUND_REFRESH) != 0) {
+ workspace.refreshManager.refresh(this);
+ monitor.worked(Policy.opWork * 90 / 100);
+ } else {
+ refreshLocal(DEPTH_INFINITE, Policy.subMonitorFor(monitor, Policy.opWork * 90 / 100));
+ }
+ } else
+ monitor.worked(Policy.opWork * 90 / 100);
+ } catch (OperationCanceledException e) {
+ workspace.getWorkManager().operationCanceled();
+ throw e;
+ } finally {
+ workspace.endOperation(rule, true, Policy.subMonitorFor(monitor, Policy.endOpWork));
+ }
+ } finally {
+ monitor.done();
+ }
+ return filter;
+ }
+
+ @Override
+ public boolean exists(IPath childPath) {
+ return workspace.getResourceInfo(getFullPath().append(childPath), false, false) != null;
+ }
+
+ @Override
+ public IResource findMember(String memberPath) {
+ return findMember(memberPath, false);
+ }
+
+ @Override
+ public IResource findMember(String memberPath, boolean phantom) {
+ IPath childPath = getFullPath().append(memberPath);
+ ResourceInfo info = workspace.getResourceInfo(childPath, phantom, false);
+ return info == null ? null : workspace.newResource(childPath, info.getType());
+ }
+
+ @Override
+ public IResource findMember(IPath childPath) {
+ return findMember(childPath, false);
+ }
+
+ @Override
+ public IResource findMember(IPath childPath, boolean phantom) {
+ childPath = getFullPath().append(childPath);
+ ResourceInfo info = workspace.getResourceInfo(childPath, phantom, false);
+ return (info == null) ? null : workspace.newResource(childPath, info.getType());
+ }
+
+ @Override
+ protected void fixupAfterMoveSource() throws CoreException {
+ super.fixupAfterMoveSource();
+ if (!synchronizing(getResourceInfo(true, false)))
+ return;
+ IResource[] members = members(IContainer.INCLUDE_PHANTOMS | IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS | IContainer.INCLUDE_HIDDEN);
+ for (int i = 0; i < members.length; i++)
+ ((Resource) members[i]).fixupAfterMoveSource();
+ }
+
+ protected IResource[] getChildren(int memberFlags) {
+ IPath[] children = null;
+ try {
+ children = workspace.tree.getChildren(path);
+ } catch (IllegalArgumentException e) {
+ //concurrency problem: the container has been deleted by another
+ //thread during this call. Just return empty children set
+ }
+ if (children == null || children.length == 0)
+ return ICoreConstants.EMPTY_RESOURCE_ARRAY;
+ Resource[] result = new Resource[children.length];
+ int found = 0;
+ for (int i = 0; i < children.length; i++) {
+ ResourceInfo info = workspace.getResourceInfo(children[i], true, false);
+ if (info != null && isMember(info.getFlags(), memberFlags))
+ result[found++] = workspace.newResource(children[i], info.getType());
+ }
+ if (found == result.length)
+ return result;
+ Resource[] trimmedResult = new Resource[found];
+ System.arraycopy(result, 0, trimmedResult, 0, found);
+ return trimmedResult;
+ }
+
+ public IFile getFile(String name) {
+ return (IFile) workspace.newResource(getFullPath().append(name), FILE);
+ }
+
+ @Override
+ public IResourceFilterDescription[] getFilters() throws CoreException {
+ IResourceFilterDescription[] results = null;
+ checkValidPath(path, FOLDER | PROJECT, true);
+ Project project = (Project) getProject();
+ ProjectDescription desc = project.internalGetDescription();
+ if (desc != null) {
+ LinkedList list = desc.getFilter(getProjectRelativePath());
+ if (list != null) {
+ results = new IResourceFilterDescription[list.size()];
+ for (int i = 0; i < list.size(); i++) {
+ results[i] = list.get(i);
+ }
+ return results;
+ }
+ }
+ return new IResourceFilterDescription[0];
+ }
+
+ public boolean hasFilters() {
+ IProject project = getProject();
+ if (project == null)
+ return false;
+ ProjectDescription desc = ((Project) project).internalGetDescription();
+ if (desc == null)
+ return false;
+ LinkedList filters = desc.getFilter(getProjectRelativePath());
+ if ((filters != null) && (filters.size() > 0))
+ return true;
+ return false;
+ }
+
+ @Override
+ public IFile getFile(IPath childPath) {
+ return (IFile) workspace.newResource(getFullPath().append(childPath), FILE);
+ }
+
+ public IFolder getFolder(String name) {
+ return (IFolder) workspace.newResource(getFullPath().append(name), FOLDER);
+ }
+
+ @Override
+ public IFolder getFolder(IPath childPath) {
+ return (IFolder) workspace.newResource(getFullPath().append(childPath), FOLDER);
+ }
+
+ @Deprecated
+ @Override
+ public boolean isLocal(int flags, int depth) {
+ if (!super.isLocal(flags, depth))
+ return false;
+ if (depth == DEPTH_ZERO)
+ return true;
+ if (depth == DEPTH_ONE)
+ depth = DEPTH_ZERO;
+ // get the children via the workspace since we know that this
+ // resource exists (it is local).
+ IResource[] children = getChildren(IResource.NONE);
+ for (int i = 0; i < children.length; i++)
+ if (!children[i].isLocal(depth))
+ return false;
+ return true;
+ }
+
+ @Override
+ public IResource[] members() throws CoreException {
+ // forward to central method
+ return members(IResource.NONE);
+ }
+
+ @Override
+ public IResource[] members(boolean phantom) throws CoreException {
+ // forward to central method
+ return members(phantom ? INCLUDE_PHANTOMS : IResource.NONE);
+ }
+
+ @Override
+ public IResource[] members(int memberFlags) throws CoreException {
+ final boolean phantom = (memberFlags & INCLUDE_PHANTOMS) != 0;
+ ResourceInfo info = getResourceInfo(phantom, false);
+ checkAccessible(getFlags(info));
+ //if children are currently unknown, ask for immediate refresh
+ if (info.isSet(ICoreConstants.M_CHILDREN_UNKNOWN))
+ workspace.refreshManager.refresh(this);
+ return getChildren(memberFlags);
+ }
+
+ public void removeFilter(IResourceFilterDescription filterDescription, int updateFlags, IProgressMonitor monitor) throws CoreException {
+ monitor = Policy.monitorFor(monitor);
+ try {
+ String message = NLS.bind(Messages.links_creating, getFullPath());
+ monitor.beginTask(message, Policy.totalWork);
+ Policy.checkCanceled(monitor);
+ checkValidPath(path, FOLDER | PROJECT, true);
+ final ISchedulingRule rule = workspace.getRuleFactory().createRule(this);
+ try {
+ workspace.prepareOperation(rule, monitor);
+ workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_FILTER_REMOVE, this));
+ workspace.beginOperation(true);
+ monitor.worked(Policy.opWork * 5 / 100);
+ //save the filter in the project description
+ Project project = (Project) getProject();
+ project.internalGetDescription().removeFilter(getProjectRelativePath(), (FilterDescription) filterDescription);
+ project.writeDescription(IResource.NONE);
+ monitor.worked(Policy.opWork * 5 / 100);
+
+ //refresh to discover any new resources below this linked location
+ if (getType() != IResource.FILE) {
+ //refresh either in background or foreground
+ if ((updateFlags & IResource.BACKGROUND_REFRESH) != 0) {
+ workspace.refreshManager.refresh(this);
+ monitor.worked(Policy.opWork * 90 / 100);
+ } else {
+ refreshLocal(DEPTH_INFINITE, Policy.subMonitorFor(monitor, Policy.opWork * 90 / 100));
+ }
+ } else
+ monitor.worked(Policy.opWork * 90 / 100);
+ } catch (OperationCanceledException e) {
+ workspace.getWorkManager().operationCanceled();
+ throw e;
+ } finally {
+ workspace.endOperation(rule, true, Policy.subMonitorFor(monitor, Policy.endOpWork));
+ }
+ } finally {
+ monitor.done();
+ }
+ }
+
+ @Override
+ public String getDefaultCharset() throws CoreException {
+ return getDefaultCharset(true);
+ }
+
+ @Override
+ public IFile[] findDeletedMembersWithHistory(int depth, IProgressMonitor monitor) {
+ IHistoryStore historyStore = getLocalManager().getHistoryStore();
+ IPath basePath = getFullPath();
+ IWorkspaceRoot root = getWorkspace().getRoot();
+ Set deletedFiles = new HashSet();
+
+ if (depth == IResource.DEPTH_ZERO) {
+ // this folder might have been a file in a past life
+ if (historyStore.getStates(basePath, monitor).length > 0) {
+ IFile file = root.getFile(basePath);
+ if (!file.exists()) {
+ deletedFiles.add(file);
+ }
+ }
+ } else {
+ // convert IPaths to IFiles keeping only files that no longer exist
+ for (IPath filePath : historyStore.allFiles(basePath, depth, monitor)) {
+ IFile file = root.getFile(filePath);
+ if (!file.exists()) {
+ deletedFiles.add(file);
+ }
+ }
+ }
+ return deletedFiles.toArray(new IFile[deletedFiles.size()]);
+ }
+
+ @Deprecated
+ @Override
+ public void setDefaultCharset(String charset) throws CoreException {
+ ResourceInfo info = getResourceInfo(false, false);
+ checkAccessible(getFlags(info));
+ workspace.getCharsetManager().setCharsetFor(getFullPath(), charset);
+ }
+
+ @Override
+ public void setDefaultCharset(String newCharset, IProgressMonitor monitor) throws CoreException {
+ monitor = Policy.monitorFor(monitor);
+ try {
+ String message = NLS.bind(Messages.resources_settingDefaultCharsetContainer, getFullPath());
+ monitor.beginTask(message, Policy.totalWork);
+ // need to get the project as a scheduling rule because we might be
+ // creating a new folder/file to hold the project settings
+ final ISchedulingRule rule = workspace.getRuleFactory().charsetRule(this);
+ try {
+ workspace.prepareOperation(rule, monitor);
+ checkAccessible(getFlags(getResourceInfo(false, false)));
+ workspace.beginOperation(true);
+ workspace.getCharsetManager().setCharsetFor(getFullPath(), newCharset);
+ // now propagate the changes to all children inheriting their setting from this container
+ IElementContentVisitor visitor = new IElementContentVisitor() {
+ boolean visitedRoot = false;
+
+ @Override
+ public boolean visitElement(ElementTree tree, IPathRequestor requestor, Object elementContents) {
+ if (elementContents == null)
+ return false;
+ IPath nodePath = requestor.requestPath();
+ // we will always generate an event at least for the root of the sub tree
+ // (skip visiting the root because we already have set the charset above and
+ // that is the condition we are checking later)
+ if (!visitedRoot) {
+ visitedRoot = true;
+ ResourceInfo info = workspace.getResourceInfo(nodePath, false, true);
+ if (info == null)
+ return false;
+ info.incrementCharsetGenerationCount();
+ return true;
+ }
+ // does it already have an encoding explicitly set?
+ if (workspace.getCharsetManager().getCharsetFor(nodePath, false) != null)
+ return false;
+ ResourceInfo info = workspace.getResourceInfo(nodePath, false, true);
+ if (info == null)
+ return false;
+ info.incrementCharsetGenerationCount();
+ return true;
+ }
+ };
+ try {
+ new ElementTreeIterator(workspace.getElementTree(), getFullPath()).iterate(visitor);
+ } catch (WrappedRuntimeException e) {
+ throw (CoreException) e.getTargetException();
+ }
+ monitor.worked(Policy.opWork);
+ } catch (OperationCanceledException e) {
+ workspace.getWorkManager().operationCanceled();
+ throw e;
+ } finally {
+ workspace.endOperation(rule, true, Policy.subMonitorFor(monitor, Policy.endOpWork));
+ }
+ } finally {
+ monitor.done();
+ }
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ContentDescriptionManager.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ContentDescriptionManager.java
new file mode 100644
index 0000000000..093c7089fb
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ContentDescriptionManager.java
@@ -0,0 +1,547 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2015 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * James Blackburn (Broadcom Corp.) - ongoing development
+ *******************************************************************************/
+package org.eclipse.core.internal.resources;
+
+import java.io.*;
+import java.util.ArrayList;
+import java.util.List;
+import org.eclipse.core.filesystem.EFS;
+import org.eclipse.core.filesystem.IFileStore;
+import org.eclipse.core.internal.events.ILifecycleListener;
+import org.eclipse.core.internal.events.LifecycleEvent;
+import org.eclipse.core.internal.utils.*;
+import org.eclipse.core.internal.watson.*;
+import org.eclipse.core.resources.*;
+import org.eclipse.core.runtime.*;
+import org.eclipse.core.runtime.content.*;
+import org.eclipse.core.runtime.content.IContentTypeManager.ContentTypeChangeEvent;
+import org.eclipse.core.runtime.jobs.ISchedulingRule;
+import org.eclipse.osgi.util.NLS;
+import org.osgi.framework.Bundle;
+
+/**
+ * Keeps a cache of recently read content descriptions.
+ *
+ * @since 3.0
+ * @see IFile#getContentDescription()
+ */
+public class ContentDescriptionManager implements IManager, IRegistryChangeListener, IContentTypeManager.IContentTypeChangeListener, ILifecycleListener {
+ /**
+ * This job causes the content description cache and the related flags
+ * in the resource tree to be flushed.
+ */
+ private class FlushJob extends WorkspaceJob {
+ private final List toFlush;
+ private boolean fullFlush;
+
+ public FlushJob() {
+ super(Messages.resources_flushingContentDescriptionCache);
+ setSystem(true);
+ setUser(false);
+ setPriority(LONG);
+ setRule(workspace.getRoot());
+ toFlush = new ArrayList(5);
+ }
+
+ /* (non-Javadoc)
+ * See Job#belongsTo(Object)
+ */
+ @Override
+ public boolean belongsTo(Object family) {
+ return FAMILY_DESCRIPTION_CACHE_FLUSH.equals(family);
+ }
+
+ /* (non-Javadoc)
+ * See WorkspaceJob#runInWorkspace(IProgressMonitor)
+ */
+ @Override
+ public IStatus runInWorkspace(final IProgressMonitor monitor) {
+ if (monitor.isCanceled())
+ return Status.CANCEL_STATUS;
+ try {
+ monitor.beginTask("", Policy.opWork); //$NON-NLS-1$
+ //note that even though we are running in a workspace job, we
+ //must do a begin/endOperation to re-acquire the workspace lock
+ final ISchedulingRule rule = workspace.getRoot();
+ try {
+ workspace.prepareOperation(rule, monitor);
+ workspace.beginOperation(true);
+ //don't do anything if the system is shutting down or has been shut down
+ //it is too late to change the workspace at this point anyway
+ if (systemBundle.getState() != Bundle.STOPPING)
+ doFlushCache(monitor, getPathsToFlush());
+ } finally {
+ workspace.endOperation(rule, false, Policy.subMonitorFor(monitor, Policy.endOpWork));
+ }
+ } catch (OperationCanceledException e) {
+ return Status.CANCEL_STATUS;
+ } catch (CoreException e) {
+ return e.getStatus();
+ } finally {
+ monitor.done();
+ }
+ return Status.OK_STATUS;
+ }
+
+ private IPath[] getPathsToFlush() {
+ synchronized (toFlush) {
+ try {
+ if (fullFlush)
+ return null;
+ int size = toFlush.size();
+ return (size == 0) ? null : toFlush.toArray(new IPath[size]);
+ } finally {
+ fullFlush = false;
+ toFlush.clear();
+ }
+ }
+ }
+
+ /**
+ * @param project project to flush, or null for a full flush
+ */
+ void flush(IProject project) {
+ if (Policy.DEBUG_CONTENT_TYPE_CACHE)
+ Policy.debug("Scheduling flushing of content type cache for " + (project == null ? Path.ROOT : project.getFullPath())); //$NON-NLS-1$
+ synchronized (toFlush) {
+ if (!fullFlush)
+ if (project == null)
+ fullFlush = true;
+ else
+ toFlush.add(project.getFullPath());
+ }
+ schedule(1000);
+ }
+
+ }
+
+ /**
+ * An input stream that only opens the file if bytes are actually requested.
+ * @see #readDescription(File)
+ */
+ class LazyFileInputStream extends InputStream {
+ private InputStream actual;
+ private IFileStore target;
+
+ LazyFileInputStream(IFileStore target) {
+ this.target = target;
+ }
+
+ @Override
+ public int available() throws IOException {
+ if (actual == null)
+ return 0;
+ return actual.available();
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (actual == null)
+ return;
+ actual.close();
+ }
+
+ private void ensureOpened() throws IOException {
+ if (actual != null)
+ return;
+ if (target == null)
+ throw new FileNotFoundException();
+ try {
+ actual = target.openInputStream(EFS.NONE, null);
+ } catch (CoreException e) {
+ throw new IOException(e.getMessage());
+ }
+ }
+
+ @Override
+ public int read() throws IOException {
+ ensureOpened();
+ return actual.read();
+ }
+
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+ ensureOpened();
+ return actual.read(b, off, len);
+ }
+
+ @Override
+ public long skip(long n) throws IOException {
+ ensureOpened();
+ return actual.skip(n);
+ }
+ }
+
+ private static final QualifiedName CACHE_STATE = new QualifiedName(ResourcesPlugin.PI_RESOURCES, "contentCacheState"); //$NON-NLS-1$
+ private static final QualifiedName CACHE_TIMESTAMP = new QualifiedName(ResourcesPlugin.PI_RESOURCES, "contentCacheTimestamp"); //$NON-NLS-1$\
+
+ public static final String FAMILY_DESCRIPTION_CACHE_FLUSH = ResourcesPlugin.PI_RESOURCES + ".contentDescriptionCacheFamily"; //$NON-NLS-1$
+
+ //possible values for the CACHE_STATE property
+ public static final byte EMPTY_CACHE = 1;
+ public static final byte USED_CACHE = 2;
+ public static final byte INVALID_CACHE = 3;
+ public static final byte FLUSHING_CACHE = 4;
+
+ // This state indicates that FlushJob is scheduled and full flush is going to be performed.
+ // In the meantime the cache was discarded. It is used as a temporary cache till the FlushJob start.
+ public static final byte ABOUT_TO_FLUSH = 5;
+
+ private static final String PT_CONTENTTYPES = "contentTypes"; //$NON-NLS-1$
+
+ private Cache cache;
+
+ private byte cacheState;
+
+ private FlushJob flushJob;
+ private ProjectContentTypes projectContentTypes;
+
+ Workspace workspace;
+ protected final Bundle systemBundle = Platform.getBundle("org.eclipse.osgi"); //$NON-NLS-1$
+
+ /**
+ * @see org.eclipse.core.runtime.content.IContentTypeManager.IContentTypeChangeListener#contentTypeChanged(IContentTypeManager.ContentTypeChangeEvent)
+ */
+ @Override
+ public void contentTypeChanged(ContentTypeChangeEvent event) {
+ if (Policy.DEBUG_CONTENT_TYPE)
+ Policy.debug("Content type settings changed for " + event.getContentType()); //$NON-NLS-1$
+ invalidateCache(true, null);
+ }
+
+ synchronized void doFlushCache(final IProgressMonitor monitor, IPath[] toClean) throws CoreException {
+ // nothing to be done if no information cached
+ if (getCacheState() != INVALID_CACHE && getCacheState() != ABOUT_TO_FLUSH) {
+ if (Policy.DEBUG_CONTENT_TYPE_CACHE)
+ Policy.debug("Content type cache flush not performed"); //$NON-NLS-1$
+ return;
+ }
+ try {
+ setCacheState(FLUSHING_CACHE);
+ // flush the MRU cache
+ cache.discardAll();
+ if (toClean == null || toClean.length == 0)
+ // no project was added, must be a global flush
+ clearContentFlags(Path.ROOT, monitor);
+ else {
+ // flush a project at a time
+ for (int i = 0; i < toClean.length; i++)
+ clearContentFlags(toClean[i], monitor);
+ }
+ } catch (CoreException ce) {
+ setCacheState(INVALID_CACHE);
+ throw ce;
+ }
+ // done cleaning (only if we didn't fail)
+ setCacheState(EMPTY_CACHE);
+ }
+
+ /**
+ * Clears the content related flags for every file under the given root.
+ */
+ private void clearContentFlags(IPath root, final IProgressMonitor monitor) {
+ long flushStart = System.currentTimeMillis();
+ if (Policy.DEBUG_CONTENT_TYPE_CACHE)
+ Policy.debug("Flushing content type cache for " + root); //$NON-NLS-1$
+ // discard content type related flags for all files in the tree
+ IElementContentVisitor visitor = new IElementContentVisitor() {
+ @Override
+ public boolean visitElement(ElementTree tree, IPathRequestor requestor, Object elementContents) {
+ if (monitor.isCanceled())
+ throw new OperationCanceledException();
+ if (elementContents == null)
+ return false;
+ ResourceInfo info = (ResourceInfo) elementContents;
+ if (info.getType() != IResource.FILE)
+ return true;
+ info = workspace.getResourceInfo(requestor.requestPath(), false, true);
+ if (info == null)
+ return false;
+ info.clear(ICoreConstants.M_CONTENT_CACHE);
+ return true;
+ }
+ };
+ new ElementTreeIterator(workspace.getElementTree(), root).iterate(visitor);
+ if (Policy.DEBUG_CONTENT_TYPE_CACHE)
+ Policy.debug("Content type cache for " + root + " flushed in " + (System.currentTimeMillis() - flushStart) + " ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+
+ Cache getCache() {
+ return cache;
+ }
+
+ /** Public so tests can examine it. */
+ public synchronized byte getCacheState() {
+ if (cacheState != 0)
+ // we have read/set it before, no nead to read property
+ return cacheState;
+ String persisted;
+ try {
+ persisted = workspace.getRoot().getPersistentProperty(CACHE_STATE);
+ cacheState = persisted != null ? Byte.parseByte(persisted) : INVALID_CACHE;
+ } catch (NumberFormatException e) {
+ cacheState = INVALID_CACHE;
+ } catch (CoreException e) {
+ Policy.log(e.getStatus());
+ cacheState = INVALID_CACHE;
+ }
+ return cacheState;
+ }
+
+ public long getCacheTimestamp() throws CoreException {
+ try {
+ return Long.parseLong(workspace.getRoot().getPersistentProperty(CACHE_TIMESTAMP));
+ } catch (NumberFormatException e) {
+ return 0;
+ }
+ }
+
+ public IContentTypeMatcher getContentTypeMatcher(Project project) throws CoreException {
+ return projectContentTypes.getMatcherFor(project);
+ }
+
+ /**
+ * Discovers, and caches, the content description of the requested File.
+ * @param file to discover the content description for; result cached
+ * @param info ResourceInfo for the passed in file
+ * @param inSync boolean flag which indicates if cache can be trusted. If false false don't trust the cache
+ * @return IContentDescription for the file
+ * @throws CoreException
+ */
+ public IContentDescription getDescriptionFor(File file, ResourceInfo info, boolean inSync) throws CoreException {
+ if (ProjectContentTypes.usesContentTypePreferences(file.getFullPath().segment(0)))
+ // caching for project containing project specific settings is not supported
+ return readDescription(file);
+ if (getCacheState() == INVALID_CACHE) {
+ // discard the cache, so it can be used before the flush job starts
+ setCacheState(ABOUT_TO_FLUSH);
+ cache.discardAll();
+ // the cache is not good, flush it
+ flushJob.schedule(1000);
+ }
+ if (inSync && getCacheState() != ABOUT_TO_FLUSH) {
+ // first look for the flags in the resource info to avoid looking in the cache
+ // don't need to copy the info because the modified bits are not in the deltas
+ if (info == null)
+ return null;
+ if (info.isSet(ICoreConstants.M_NO_CONTENT_DESCRIPTION))
+ // presumably, this file has no known content type
+ return null;
+ if (info.isSet(ICoreConstants.M_DEFAULT_CONTENT_DESCRIPTION)) {
+ // this file supposedly has a default content description for an "obvious" content type
+ IContentTypeManager contentTypeManager = Platform.getContentTypeManager();
+ // try to find the obvious content type matching its name
+ IContentType type = contentTypeManager.findContentTypeFor(file.getName());
+ if (type != null)
+ // we found it, we are done
+ return type.getDefaultDescription();
+ // for some reason, there was no content type for this file name
+ // fix this and keep going
+ info.clear(ICoreConstants.M_CONTENT_CACHE);
+ }
+ }
+ if (inSync) {
+ // tries to get a description from the cache
+ synchronized (this) {
+ Cache.Entry entry = cache.getEntry(file.getFullPath());
+ if (entry != null && entry.getTimestamp() == getTimestamp(info))
+ // there was a description in the cache, and it was up to date
+ return (IContentDescription) entry.getCached();
+ }
+ }
+
+ // either we didn't find a description in the cache, or it was not up-to-date - has to be read again
+ // reading description can call 3rd party code, so don't synchronize it
+ IContentDescription newDescription = readDescription(file);
+
+ synchronized (this) {
+ // tries to get a description from the cache
+ Cache.Entry entry = cache.getEntry(file.getFullPath());
+ if (entry != null && inSync && entry.getTimestamp() == getTimestamp(info))
+ // there was a description in the cache, and it was up to date
+ return (IContentDescription) entry.getCached();
+
+ if (getCacheState() != ABOUT_TO_FLUSH) {
+ // we are going to add an entry to the cache or update the resource info - remember that
+ setCacheState(USED_CACHE);
+ if (newDescription == null) {
+ // no content type exists for this file name/contents - remember this
+ info.set(ICoreConstants.M_NO_CONTENT_DESCRIPTION);
+ return null;
+ }
+ if (newDescription.getContentType().getDefaultDescription().equals(newDescription)) {
+ // we got a default description
+ IContentType defaultForName = Platform.getContentTypeManager().findContentTypeFor(file.getName());
+ if (newDescription.getContentType().equals(defaultForName)) {
+ // it is a default description for the obvious content type given its file name, we don't have to cache
+ info.set(ICoreConstants.M_DEFAULT_CONTENT_DESCRIPTION);
+ return newDescription;
+ }
+ }
+ }
+ // we actually got a description filled by a describer (or a default description for a non-obvious type)
+ if (entry == null)
+ // there was no entry before - create one
+ entry = cache.addEntry(file.getFullPath(), newDescription, getTimestamp(info));
+ else {
+ // just update the existing entry
+ entry.setTimestamp(getTimestamp(info));
+ entry.setCached(newDescription);
+ }
+ return newDescription;
+ }
+ }
+
+ /**
+ * Returns a timestamp that uniquely identifies a particular content state
+ * of a particular resource. For use as a key in a content type cache.
+ */
+ private long getTimestamp(ResourceInfo info) {
+ return info.getContentId() + info.getNodeId();
+ }
+
+ /**
+ * Marks the cache as invalid. Does not do anything if the cache is new.
+ * Optionally causes the cached information to be actually flushed.
+ *
+ * @param flush whether the cached information should be flushed
+ * @see #doFlushCache(IProgressMonitor, IPath[])
+ */
+ public synchronized void invalidateCache(boolean flush, IProject project) {
+ if (getCacheState() == EMPTY_CACHE)
+ // cache has not been touched, nothing to do
+ return;
+ // mark the cache as invalid
+ try {
+ setCacheState(INVALID_CACHE);
+ } catch (CoreException e) {
+ Policy.log(e.getStatus());
+ }
+ if (Policy.DEBUG_CONTENT_TYPE_CACHE)
+ Policy.debug("Invalidated cache for " + (project == null ? Path.ROOT : project.getFullPath())); //$NON-NLS-1$
+ if (flush) {
+ try {
+ // discard the cache, so it can be used before the flush job starts
+ setCacheState(ABOUT_TO_FLUSH);
+ cache.discardAll();
+ } catch (CoreException e) {
+ Policy.log(e.getStatus());
+ }
+ // the cache is not good, flush it
+ flushJob.flush(project);
+ }
+ }
+
+ /**
+ * Tries to obtain a content description for the given file.
+ */
+ private IContentDescription readDescription(File file) throws CoreException {
+ if (Policy.DEBUG_CONTENT_TYPE)
+ Policy.debug("reading contents of " + file); //$NON-NLS-1$
+ // tries to obtain a description for this file contents
+ InputStream contents = new LazyFileInputStream(file.getStore());
+ try {
+ IContentTypeMatcher matcher = getContentTypeMatcher((Project) file.getProject());
+ return matcher.getDescriptionFor(contents, file.getName(), IContentDescription.ALL);
+ } catch (IOException e) {
+ String message = NLS.bind(Messages.resources_errorContentDescription, file.getFullPath());
+ throw new ResourceException(IResourceStatus.FAILED_DESCRIBING_CONTENTS, file.getFullPath(), message, e);
+ } finally {
+ FileUtil.safeClose(contents);
+ }
+ }
+
+ /**
+ * @see IRegistryChangeListener#registryChanged(IRegistryChangeEvent)
+ */
+ @Override
+ public void registryChanged(IRegistryChangeEvent event) {
+ // no changes related to the content type registry
+ if (event.getExtensionDeltas(Platform.PI_RUNTIME, PT_CONTENTTYPES).length == 0)
+ return;
+ invalidateCache(true, null);
+ }
+
+ /**
+ * @see ILifecycleListener#handleEvent(LifecycleEvent)
+ */
+ @Override
+ public void handleEvent(LifecycleEvent event) {
+ //TODO are these the only events we care about?
+ switch (event.kind) {
+ case LifecycleEvent.POST_PROJECT_CHANGE :
+ // if the project changes, its natures may have changed as well (content types may be associated to natures)
+ case LifecycleEvent.PRE_PROJECT_DELETE :
+ // if the project gets deleted, we may get confused if it is recreated again (content ids might match)
+ case LifecycleEvent.PRE_PROJECT_MOVE :
+ // if the project moves, resource paths (used as keys in the in-memory cache) will have changed
+ invalidateCache(true, (IProject) event.resource);
+ }
+ }
+
+ synchronized void setCacheState(byte newCacheState) throws CoreException {
+ if (cacheState == newCacheState)
+ return;
+ workspace.getRoot().setPersistentProperty(CACHE_STATE, Byte.toString(newCacheState));
+ cacheState = newCacheState;
+ }
+
+ private void setCacheTimeStamp(long timeStamp) throws CoreException {
+ workspace.getRoot().setPersistentProperty(CACHE_TIMESTAMP, Long.toString(timeStamp));
+ }
+
+ @Override
+ public void shutdown(IProgressMonitor monitor) throws CoreException {
+ if (getCacheState() != INVALID_CACHE)
+ // remember the platform timestamp for which we have a valid cache
+ setCacheTimeStamp(Platform.getStateStamp());
+ IContentTypeManager contentTypeManager = Platform.getContentTypeManager();
+ //tolerate missing services during shutdown because they might be already gone
+ if (contentTypeManager != null)
+ contentTypeManager.removeContentTypeChangeListener(this);
+ IExtensionRegistry registry = Platform.getExtensionRegistry();
+ if (registry != null)
+ registry.removeRegistryChangeListener(this);
+ cache.dispose();
+ cache = null;
+ flushJob.cancel();
+ flushJob = null;
+ projectContentTypes = null;
+ }
+
+ @Override
+ public void startup(IProgressMonitor monitor) throws CoreException {
+ workspace = (Workspace) ResourcesPlugin.getWorkspace();
+ cache = new Cache(100, 1000, 0.1);
+ projectContentTypes = new ProjectContentTypes(workspace);
+ getCacheState();
+ if (cacheState == FLUSHING_CACHE || cacheState == ABOUT_TO_FLUSH)
+ // in case we died before completing the last flushing
+ setCacheState(INVALID_CACHE);
+ flushJob = new FlushJob();
+ // the cache is stale (plug-ins that might be contributing content types were added/removed)
+ if (getCacheTimestamp() != Platform.getStateStamp())
+ invalidateCache(false, null);
+ // register a lifecycle listener
+ workspace.addLifecycleListener(this);
+ // register a content type change listener
+ Platform.getContentTypeManager().addContentTypeChangeListener(this);
+ // register a registry change listener
+ Platform.getExtensionRegistry().addRegistryChangeListener(this, Platform.PI_RUNTIME);
+ }
+
+ public void projectPreferencesChanged(IProject project) {
+ if (Policy.DEBUG_CONTENT_TYPE)
+ Policy.debug("Project preferences changed for " + project); //$NON-NLS-1$
+ projectContentTypes.contentTypePreferencesChanged(project);
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/DelayedSnapshotJob.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/DelayedSnapshotJob.java
new file mode 100644
index 0000000000..995f03ef8c
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/DelayedSnapshotJob.java
@@ -0,0 +1,60 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2014 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.resources;
+
+import org.eclipse.core.internal.utils.Messages;
+import org.eclipse.core.internal.utils.Policy;
+import org.eclipse.core.resources.ISaveContext;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.*;
+import org.eclipse.core.runtime.jobs.Job;
+
+/**
+ * Performs periodic saving (snapshot) of the workspace.
+ */
+public class DelayedSnapshotJob extends Job {
+
+ private static final String MSG_SNAPSHOT = Messages.resources_snapshot;
+ private SaveManager saveManager;
+
+ public DelayedSnapshotJob(SaveManager manager) {
+ super(MSG_SNAPSHOT);
+ this.saveManager = manager;
+ setRule(ResourcesPlugin.getWorkspace().getRoot());
+ setSystem(true);
+ }
+
+ /*
+ * @see Job#run()
+ */
+ @Override
+ public IStatus run(IProgressMonitor monitor) {
+ if (monitor.isCanceled())
+ return Status.CANCEL_STATUS;
+
+ try {
+ ResourcesPlugin.getWorkspace();
+ } catch (IllegalStateException e) {
+ // workspace is null, log it as warning only and return OK_STATUS
+ Policy.log(IStatus.WARNING, null, e);
+ return Status.OK_STATUS;
+ }
+
+ try {
+ return saveManager.save(ISaveContext.SNAPSHOT, null, Policy.monitorFor(null));
+ } catch (CoreException e) {
+ return e.getStatus();
+ } finally {
+ saveManager.operationCount = 0;
+ saveManager.snapshotRequested = false;
+ }
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/File.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/File.java
new file mode 100644
index 0000000000..89dd89e1bf
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/File.java
@@ -0,0 +1,436 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2015 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * Serge Beauchamp (Freescale Semiconductor) - [229633] Group and Project Path Variable Support
+ * James Blackburn (Broadcom Corp.) - ongoing development
+ *******************************************************************************/
+package org.eclipse.core.internal.resources;
+
+import java.io.*;
+import org.eclipse.core.filesystem.*;
+import org.eclipse.core.internal.preferences.EclipsePreferences;
+import org.eclipse.core.internal.utils.*;
+import org.eclipse.core.resources.*;
+import org.eclipse.core.runtime.*;
+import org.eclipse.core.runtime.content.IContentDescription;
+import org.eclipse.core.runtime.content.IContentTypeManager;
+import org.eclipse.core.runtime.jobs.ISchedulingRule;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * The standard implementation of {@link IFile}.
+ */
+public class File extends Resource implements IFile {
+
+ protected File(IPath path, Workspace container) {
+ super(path, container);
+ }
+
+ @Override
+ public void appendContents(InputStream content, int updateFlags, IProgressMonitor monitor) throws CoreException {
+ monitor = Policy.monitorFor(monitor);
+ try {
+ String message = NLS.bind(Messages.resources_settingContents, getFullPath());
+ monitor.beginTask(message, Policy.totalWork);
+ Assert.isNotNull(content, "Content cannot be null."); //$NON-NLS-1$
+ if (workspace.shouldValidate)
+ workspace.validateSave(this);
+ final ISchedulingRule rule = workspace.getRuleFactory().modifyRule(this);
+ try {
+ workspace.prepareOperation(rule, monitor);
+ ResourceInfo info = getResourceInfo(false, false);
+ checkAccessible(getFlags(info));
+ workspace.beginOperation(true);
+ IFileInfo fileInfo = getStore().fetchInfo();
+ internalSetContents(content, fileInfo, updateFlags, true, Policy.subMonitorFor(monitor, Policy.opWork));
+ } catch (OperationCanceledException e) {
+ workspace.getWorkManager().operationCanceled();
+ throw e;
+ } finally {
+ workspace.endOperation(rule, true, Policy.subMonitorFor(monitor, Policy.endOpWork));
+ }
+ } finally {
+ monitor.done();
+ FileUtil.safeClose(content);
+ }
+ }
+
+ @Override
+ public void appendContents(InputStream content, boolean force, boolean keepHistory, IProgressMonitor monitor) throws CoreException {
+ // funnel all operations to central method
+ int updateFlags = force ? IResource.FORCE : IResource.NONE;
+ updateFlags |= keepHistory ? IResource.KEEP_HISTORY : IResource.NONE;
+ appendContents(content, updateFlags, monitor);
+ }
+
+ /**
+ * Changes this file to be a folder in the resource tree and returns
+ * the newly created folder. All related
+ * properties are deleted. It is assumed that on disk the resource is
+ * already a folder/directory so no action is taken to delete the disk
+ * contents.
+ *
+ * This method is for the exclusive use of the local resource manager
+ */
+ public IFolder changeToFolder() throws CoreException {
+ getPropertyManager().deleteProperties(this, IResource.DEPTH_ZERO);
+ IFolder result = workspace.getRoot().getFolder(path);
+ if (isLinked()) {
+ IPath location = getRawLocation();
+ delete(IResource.NONE, null);
+ result.createLink(location, IResource.ALLOW_MISSING_LOCAL, null);
+ } else {
+ workspace.deleteResource(this);
+ workspace.createResource(result, false);
+ }
+ return result;
+ }
+
+ @Override
+ public void create(InputStream content, int updateFlags, IProgressMonitor monitor) throws CoreException {
+ final boolean monitorNull = monitor == null;
+ monitor = Policy.monitorFor(monitor);
+ try {
+ String message = monitorNull ? "" : NLS.bind(Messages.resources_creating, getFullPath()); //$NON-NLS-1$
+ monitor.beginTask(message, Policy.totalWork);
+ checkValidPath(path, FILE, true);
+ final ISchedulingRule rule = workspace.getRuleFactory().createRule(this);
+ try {
+ workspace.prepareOperation(rule, monitor);
+ checkDoesNotExist();
+ Container parent = (Container) getParent();
+ ResourceInfo info = parent.getResourceInfo(false, false);
+ parent.checkAccessible(getFlags(info));
+ checkValidGroupContainer(parent, false, false);
+
+ workspace.beginOperation(true);
+ IFileStore store = getStore();
+ IFileInfo localInfo = store.fetchInfo();
+ if (BitMask.isSet(updateFlags, IResource.FORCE)) {
+ if (!Workspace.caseSensitive) {
+ if (localInfo.exists()) {
+ String name = getLocalManager().getLocalName(store);
+ if (name == null || localInfo.getName().equals(name)) {
+ delete(true, null);
+ } else {
+ // The file system is not case sensitive and there is already a file
+ // under this location.
+ message = NLS.bind(Messages.resources_existsLocalDifferentCase, new Path(store.toString()).removeLastSegments(1).append(name).toOSString());
+ throw new ResourceException(IResourceStatus.CASE_VARIANT_EXISTS, getFullPath(), message, null);
+ }
+ }
+ }
+ } else {
+ if (localInfo.exists()) {
+ //return an appropriate error message for case variant collisions
+ if (!Workspace.caseSensitive) {
+ String name = getLocalManager().getLocalName(store);
+ if (name != null && !localInfo.getName().equals(name)) {
+ message = NLS.bind(Messages.resources_existsLocalDifferentCase, new Path(store.toString()).removeLastSegments(1).append(name).toOSString());
+ throw new ResourceException(IResourceStatus.CASE_VARIANT_EXISTS, getFullPath(), message, null);
+ }
+ }
+ message = NLS.bind(Messages.resources_fileExists, store.toString());
+ throw new ResourceException(IResourceStatus.FAILED_WRITE_LOCAL, getFullPath(), message, null);
+ }
+ }
+ monitor.worked(Policy.opWork * 40 / 100);
+
+ info = workspace.createResource(this, updateFlags);
+ boolean local = content != null;
+ if (local) {
+ try {
+ internalSetContents(content, localInfo, updateFlags, false, Policy.subMonitorFor(monitor, Policy.opWork * 60 / 100));
+ } catch (CoreException e) {
+ // a problem happened creating the file on disk, so delete from the workspace and disk
+ workspace.deleteResource(this);
+ store.delete(EFS.NONE, null);
+ throw e; // rethrow
+ } catch (OperationCanceledException e) {
+ // the operation of setting contents has been canceled, so delete the file from the workspace and disk
+ workspace.deleteResource(this);
+ store.delete(EFS.NONE, null);
+ throw e;
+ }
+ }
+ internalSetLocal(local, DEPTH_ZERO);
+ if (!local)
+ getResourceInfo(true, true).clearModificationStamp();
+ } catch (OperationCanceledException e) {
+ workspace.getWorkManager().operationCanceled();
+ throw e;
+ } finally {
+ workspace.endOperation(rule, true, Policy.subMonitorFor(monitor, Policy.endOpWork));
+ }
+ } finally {
+ monitor.done();
+ FileUtil.safeClose(content);
+ }
+ }
+
+ @Override
+ public void create(InputStream content, boolean force, IProgressMonitor monitor) throws CoreException {
+ // funnel all operations to central method
+ create(content, (force ? IResource.FORCE : IResource.NONE), monitor);
+ }
+
+ @Override
+ public String getCharset() throws CoreException {
+ return getCharset(true);
+ }
+
+ @Override
+ public String getCharset(boolean checkImplicit) throws CoreException {
+ // non-existing resources default to parent's charset
+ ResourceInfo info = getResourceInfo(false, false);
+ int flags = getFlags(info);
+ if (!exists(flags, false))
+ return checkImplicit ? workspace.getCharsetManager().getCharsetFor(getFullPath().removeLastSegments(1), true) : null;
+ checkLocal(flags, DEPTH_ZERO);
+ return internalGetCharset(checkImplicit, info);
+ }
+
+ @Override
+ public String getCharsetFor(Reader contents) throws CoreException {
+ String charset;
+ ResourceInfo info = getResourceInfo(false, false);
+ int flags = getFlags(info);
+ if (exists(flags, true))
+ // the file exists, look for user setting
+ if ((charset = workspace.getCharsetManager().getCharsetFor(getFullPath(), false)) != null)
+ // if there is a file-specific user setting, use it
+ return charset;
+ // tries to obtain a description from the contents provided
+ IContentDescription description;
+ try {
+ // TODO need to take project specific settings into account
+ IContentTypeManager contentTypeManager = Platform.getContentTypeManager();
+ description = contentTypeManager.getDescriptionFor(contents, getName(), new QualifiedName[] {IContentDescription.CHARSET});
+ } catch (IOException e) {
+ String message = NLS.bind(Messages.resources_errorContentDescription, getFullPath());
+ throw new ResourceException(IResourceStatus.FAILED_DESCRIBING_CONTENTS, getFullPath(), message, e);
+ }
+ if (description != null)
+ if ((charset = description.getCharset()) != null)
+ // the description contained charset info, we are done
+ return charset;
+ // could not find out the encoding based on the contents... default to parent's
+ return workspace.getCharsetManager().getCharsetFor(getFullPath().removeLastSegments(1), true);
+ }
+
+ private String internalGetCharset(boolean checkImplicit, ResourceInfo info) throws CoreException {
+ // if there is a file-specific user setting, use it
+ String charset = workspace.getCharsetManager().getCharsetFor(getFullPath(), false);
+ if (charset != null || !checkImplicit)
+ return charset;
+ // tries to obtain a description for the file contents
+ IContentDescription description = workspace.getContentDescriptionManager().getDescriptionFor(this, info, true);
+ if (description != null) {
+ String contentCharset = description.getCharset();
+ if (contentCharset != null)
+ return contentCharset;
+ }
+ // could not find out the encoding based on the contents... default to parent's
+ return workspace.getCharsetManager().getCharsetFor(getFullPath().removeLastSegments(1), true);
+ }
+
+ @Override
+ public IContentDescription getContentDescription() throws CoreException {
+ ResourceInfo info = getResourceInfo(false, false);
+ int flags = getFlags(info);
+ checkAccessible(flags);
+ checkLocal(flags, DEPTH_ZERO);
+ boolean isSynchronized = isSynchronized(IResource.DEPTH_ZERO);
+ // Throw an exception if out-of-sync and not auto-refresh enabled
+ if (!isSynchronized && !getLocalManager().isLightweightAutoRefreshEnabled()) {
+ String message = NLS.bind(Messages.localstore_resourceIsOutOfSync, getFullPath());
+ throw new ResourceException(IResourceStatus.OUT_OF_SYNC_LOCAL, getFullPath(), message, null);
+ }
+ return workspace.getContentDescriptionManager().getDescriptionFor(this, info, isSynchronized);
+ }
+
+ @Override
+ public InputStream getContents() throws CoreException {
+ return getContents(getLocalManager().isLightweightAutoRefreshEnabled());
+ }
+
+ @Override
+ public InputStream getContents(boolean force) throws CoreException {
+ ResourceInfo info = getResourceInfo(false, false);
+ int flags = getFlags(info);
+ checkAccessible(flags);
+ checkLocal(flags, DEPTH_ZERO);
+ return getLocalManager().read(this, force, null);
+ }
+
+ @Deprecated
+ @Override
+ public int getEncoding() throws CoreException {
+ ResourceInfo info = getResourceInfo(false, false);
+ int flags = getFlags(info);
+ checkAccessible(flags);
+ checkLocal(flags, DEPTH_ZERO);
+ return getLocalManager().getEncoding(this);
+ }
+
+ @Override
+ public IFileState[] getHistory(IProgressMonitor monitor) {
+ return getLocalManager().getHistoryStore().getStates(getFullPath(), monitor);
+ }
+
+ @Override
+ public int getType() {
+ return FILE;
+ }
+
+ protected void internalSetContents(InputStream content, IFileInfo fileInfo, int updateFlags, boolean append, IProgressMonitor monitor) throws CoreException {
+ if (content == null)
+ content = new ByteArrayInputStream(new byte[0]);
+ getLocalManager().write(this, content, fileInfo, updateFlags, append, monitor);
+ updateMetadataFiles();
+ workspace.getAliasManager().updateAliases(this, getStore(), IResource.DEPTH_ZERO, monitor);
+ }
+
+ /**
+ * Optimized refreshLocal for files. This implementation does not block the workspace
+ * for the common case where the file exists both locally and on the file system, and
+ * is in sync. For all other cases, it defers to the super implementation.
+ */
+ @Override
+ public void refreshLocal(int depth, IProgressMonitor monitor) throws CoreException {
+ if (!getLocalManager().fastIsSynchronized(this))
+ super.refreshLocal(IResource.DEPTH_ZERO, monitor);
+ }
+
+ @Override
+ public void setContents(IFileState content, int updateFlags, IProgressMonitor monitor) throws CoreException {
+ setContents(content.getContents(), updateFlags, monitor);
+ }
+
+ @Override
+ public void setContents(InputStream content, int updateFlags, IProgressMonitor monitor) throws CoreException {
+ monitor = Policy.monitorFor(monitor);
+ try {
+ String message = NLS.bind(Messages.resources_settingContents, getFullPath());
+ monitor.beginTask(message, Policy.totalWork);
+ if (workspace.shouldValidate)
+ workspace.validateSave(this);
+ final ISchedulingRule rule = workspace.getRuleFactory().modifyRule(this);
+ try {
+ workspace.prepareOperation(rule, monitor);
+ ResourceInfo info = getResourceInfo(false, false);
+ checkAccessible(getFlags(info));
+ workspace.beginOperation(true);
+ IFileInfo fileInfo = getStore().fetchInfo();
+ internalSetContents(content, fileInfo, updateFlags, false, Policy.subMonitorFor(monitor, Policy.opWork));
+ } catch (OperationCanceledException e) {
+ workspace.getWorkManager().operationCanceled();
+ throw e;
+ } finally {
+ workspace.endOperation(rule, true, Policy.subMonitorFor(monitor, Policy.endOpWork));
+ }
+ } finally {
+ monitor.done();
+ FileUtil.safeClose(content);
+ }
+ }
+
+ @Override
+ public long setLocalTimeStamp(long value) throws CoreException {
+ //override to handle changing timestamp on project description file
+ long result = super.setLocalTimeStamp(value);
+ if (path.segmentCount() == 2 && path.segment(1).equals(IProjectDescription.DESCRIPTION_FILE_NAME)) {
+ //handle concurrent project deletion
+ ResourceInfo projectInfo = ((Project) getProject()).getResourceInfo(false, false);
+ if (projectInfo != null)
+ getLocalManager().updateLocalSync(projectInfo, result);
+ }
+ return result;
+ }
+
+ /**
+ * Treat the file specially if it represents a metadata file, which includes:
+ * - project description file (.project)
+ * - project preferences files (*.prefs)
+ *
+ * This method is called whenever it is discovered that a file has
+ * been modified (added, removed, or changed).
+ */
+ public void updateMetadataFiles() throws CoreException {
+ int count = path.segmentCount();
+ String name = path.segment(1);
+ // is this a project description file?
+ if (count == 2 && name.equals(IProjectDescription.DESCRIPTION_FILE_NAME)) {
+ Project project = (Project) getProject();
+ project.updateDescription();
+ // Discard stale project natures on ProjectInfo
+ ProjectInfo projectInfo = (ProjectInfo) project.getResourceInfo(false, true);
+ projectInfo.discardNatures();
+ return;
+ }
+ // check to see if we are in the .settings directory
+ if (count == 3 && EclipsePreferences.DEFAULT_PREFERENCES_DIRNAME.equals(name)) {
+ ProjectPreferences.updatePreferences(this);
+ return;
+ }
+ }
+
+ @Deprecated
+ @Override
+ public void setCharset(String newCharset) throws CoreException {
+ ResourceInfo info = getResourceInfo(false, false);
+ checkAccessible(getFlags(info));
+ workspace.getCharsetManager().setCharsetFor(getFullPath(), newCharset);
+ }
+
+ @Override
+ public void setCharset(String newCharset, IProgressMonitor monitor) throws CoreException {
+ monitor = Policy.monitorFor(monitor);
+ try {
+ String message = NLS.bind(Messages.resources_settingCharset, getFullPath());
+ monitor.beginTask(message, Policy.totalWork);
+ // need to get the project as a scheduling rule because we might be creating a new folder/file to
+ // hold the project settings
+ final ISchedulingRule rule = workspace.getRuleFactory().charsetRule(this);
+ try {
+ workspace.prepareOperation(rule, monitor);
+ ResourceInfo info = getResourceInfo(false, false);
+ checkAccessible(getFlags(info));
+ workspace.beginOperation(true);
+ workspace.getCharsetManager().setCharsetFor(getFullPath(), newCharset);
+ info = getResourceInfo(false, true);
+ info.incrementCharsetGenerationCount();
+ monitor.worked(Policy.opWork);
+ } catch (OperationCanceledException e) {
+ workspace.getWorkManager().operationCanceled();
+ throw e;
+ } finally {
+ workspace.endOperation(rule, true, Policy.subMonitorFor(monitor, Policy.endOpWork));
+ }
+ } finally {
+ monitor.done();
+ }
+ }
+
+ @Override
+ public void setContents(InputStream content, boolean force, boolean keepHistory, IProgressMonitor monitor) throws CoreException {
+ // funnel all operations to central method
+ int updateFlags = force ? IResource.FORCE : IResource.NONE;
+ updateFlags |= keepHistory ? IResource.KEEP_HISTORY : IResource.NONE;
+ setContents(content, updateFlags, monitor);
+ }
+
+ @Override
+ public void setContents(IFileState source, boolean force, boolean keepHistory, IProgressMonitor monitor) throws CoreException {
+ // funnel all operations to central method
+ int updateFlags = force ? IResource.FORCE : IResource.NONE;
+ updateFlags |= keepHistory ? IResource.KEEP_HISTORY : IResource.NONE;
+ setContents(source.getContents(), updateFlags, monitor);
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/FileState.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/FileState.java
new file mode 100644
index 0000000000..a83d69d302
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/FileState.java
@@ -0,0 +1,129 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2015 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * James Blackburn (Broadcom Corp.) - ongoing development
+ *******************************************************************************/
+package org.eclipse.core.internal.resources;
+
+import java.io.*;
+import org.eclipse.core.internal.localstore.IHistoryStore;
+import org.eclipse.core.internal.utils.*;
+import org.eclipse.core.resources.*;
+import org.eclipse.core.runtime.*;
+import org.eclipse.core.runtime.content.IContentDescription;
+import org.eclipse.core.runtime.content.IContentTypeManager;
+import org.eclipse.osgi.util.NLS;
+
+public class FileState extends PlatformObject implements IFileState {
+ private static final IWorkspace workspace = ResourcesPlugin.getWorkspace();
+ protected long lastModified;
+ protected UniversalUniqueIdentifier uuid;
+ protected IHistoryStore store;
+ protected IPath fullPath;
+
+ public FileState(IHistoryStore store, IPath fullPath, long lastModified, UniversalUniqueIdentifier uuid) {
+ this.store = store;
+ this.lastModified = lastModified;
+ this.uuid = uuid;
+ this.fullPath = fullPath;
+ }
+
+ /* (non-Javadoc)
+ * @see IFileState#exists()
+ */
+ @Override
+ public boolean exists() {
+ return store.exists(this);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.resources.IEncodedStorage#getCharset()
+ */
+ @Override
+ public String getCharset() throws CoreException {
+ // if there is an existing file at this state's path, use the encoding of that file
+ IResource file = workspace.getRoot().findMember(fullPath);
+ if (file != null && file.getType() == IResource.FILE)
+ return ((IFile) file).getCharset();
+
+ // tries to obtain a description for the file contents
+ IContentTypeManager contentTypeManager = Platform.getContentTypeManager();
+ InputStream contents = new BufferedInputStream(getContents());
+ try {
+ IContentDescription description = contentTypeManager.getDescriptionFor(contents, getName(), new QualifiedName[] {IContentDescription.CHARSET});
+ contents.close();
+ return description == null ? null : description.getCharset();
+ } catch (IOException e) {
+ String message = NLS.bind(Messages.history_errorContentDescription, getFullPath());
+ throw new ResourceException(IResourceStatus.FAILED_DESCRIBING_CONTENTS, getFullPath(), message, e);
+ } finally {
+ FileUtil.safeClose(contents);
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see IFileState#getContents()
+ */
+ @Override
+ public InputStream getContents() throws CoreException {
+ return store.getContents(this);
+ }
+
+ /* (non-Javadoc)
+ * @see IFileState#getFullPath()
+ */
+ @Override
+ public IPath getFullPath() {
+ return fullPath;
+ }
+
+ /* (non-Javadoc)
+ * @see IFileState#getModificationTime()
+ */
+ @Override
+ public long getModificationTime() {
+ return lastModified;
+ }
+
+ /* (non-Javadoc)
+ * @see IFileState#getName()
+ */
+ @Override
+ public String getName() {
+ return fullPath.lastSegment();
+ }
+
+ public UniversalUniqueIdentifier getUUID() {
+ return uuid;
+ }
+
+ /* (non-Javadoc)
+ * @see IFileState#isReadOnly()
+ */
+ @Override
+ public boolean isReadOnly() {
+ return true;
+ }
+
+ /**
+ * Returns a string representation of this object. Used for debug only.
+ */
+ @Override
+ public String toString() {
+ StringBuffer s = new StringBuffer();
+ s.append("FileState(uuid: "); //$NON-NLS-1$
+ s.append(uuid.toString());
+ s.append(", lastModified: "); //$NON-NLS-1$
+ s.append(lastModified);
+ s.append(", path: "); //$NON-NLS-1$
+ s.append(fullPath);
+ s.append(')');
+ return s.toString();
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Filter.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Filter.java
new file mode 100644
index 0000000000..da46b1f86d
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Filter.java
@@ -0,0 +1,177 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2014 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Serge Beauchamp (Freescale Semiconductor) - initial API and implementation
+ * IBM Corporation - ongoing implementation
+ * James Blackburn (Broadcom Corp.) - ongoing development
+ * Sergey Prigogin (Google) - bug 424972
+ *******************************************************************************/
+package org.eclipse.core.internal.resources;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+import org.eclipse.core.filesystem.IFileInfo;
+import org.eclipse.core.internal.utils.Messages;
+import org.eclipse.core.internal.utils.Policy;
+import org.eclipse.core.resources.*;
+import org.eclipse.core.resources.filtermatchers.AbstractFileInfoMatcher;
+import org.eclipse.core.runtime.*;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * Class that instantiate IResourceFilter's that are stored in the project description.
+ */
+public class Filter {
+ /**
+ * A placeholder Filter provider that doesn't match any files or folders.
+ */
+ private static class MatchNothingInfoMatcher extends AbstractFileInfoMatcher {
+ public MatchNothingInfoMatcher() {
+ }
+
+ @Override
+ public boolean matches(IContainer parent, IFileInfo fileInfo) {
+ return false;
+ }
+
+ @Override
+ public void initialize(IProject project, Object arguments) {
+ // No initialization required.
+ }
+ }
+
+ FilterDescription description;
+ IProject project;
+ AbstractFileInfoMatcher provider = null;
+
+ public Filter(IProject project, FilterDescription description) {
+ this.description = description;
+ this.project = project;
+ }
+
+ public boolean match(IContainer parent, IFileInfo fileInfo) throws CoreException {
+ if (provider == null) {
+ IFilterMatcherDescriptor filterDescriptor = project.getWorkspace().getFilterMatcherDescriptor(getId());
+ if (filterDescriptor != null)
+ provider = ((FilterDescriptor) filterDescriptor).createFilter();
+ if (provider == null) {
+ String message = NLS.bind(Messages.filters_missingFilterType, getId());
+ Policy.log(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, Platform.PLUGIN_ERROR, message, new Error()));
+ // Avoid further initialization attempts by instantiating a placeholder filter
+ // provider that doesn't match any files or folders.
+ provider = new MatchNothingInfoMatcher();
+ }
+ try {
+ provider.initialize(project, description.getFileInfoMatcherDescription().getArguments());
+ } catch (CoreException e) {
+ Policy.log(e.getStatus());
+ provider = null;
+ }
+ }
+ if (provider != null)
+ return provider.matches(parent, fileInfo);
+ return false;
+ }
+
+ public boolean isFirst() {
+ IFilterMatcherDescriptor descriptor = project.getWorkspace().getFilterMatcherDescriptor(getId());
+ if (descriptor != null)
+ return descriptor.isFirstOrdering();
+ return false;
+ }
+
+ public Object getArguments() {
+ return description.getFileInfoMatcherDescription().getArguments();
+ }
+
+ public String getId() {
+ return description.getFileInfoMatcherDescription().getId();
+ }
+
+ public int getType() {
+ return description.getType();
+ }
+
+ public boolean isIncludeOnly() {
+ return (getType() & IResourceFilterDescription.INCLUDE_ONLY) != 0;
+ }
+
+ public boolean appliesTo(IFileInfo info) {
+ if (info.isDirectory())
+ return (getType() & IResourceFilterDescription.FOLDERS) != 0;
+ return (getType() & IResourceFilterDescription.FILES) != 0;
+ }
+
+ public static IFileInfo[] filter(IProject project, LinkedList includeFilters, LinkedList excludeFilters, IContainer parent, IFileInfo[] list) throws CoreException {
+ IFileInfo[] result = filterIncludes(project, includeFilters, parent, list);
+ return filterExcludes(project, excludeFilters, parent, result);
+ }
+
+ public static IFileInfo[] filterIncludes(IProject project, LinkedList filters, IContainer parent, IFileInfo[] list) throws CoreException {
+ if (filters.size() > 0) {
+ IFileInfo[] result = new IFileInfo[list.length];
+ int outputIndex = 0;
+
+ for (int i = 0; i < list.length; i++) {
+ IFileInfo info = list[i];
+ Iterator objIt = filters.iterator();
+ boolean filtersWereApplicable = false;
+ while (objIt.hasNext()) {
+ Filter filter = objIt.next();
+ if (filter.appliesTo(info)) {
+ filtersWereApplicable = true;
+ if (filter.match(parent, info)) {
+ result[outputIndex++] = info;
+ break;
+ }
+ }
+ }
+ if (!filtersWereApplicable)
+ result[outputIndex++] = info;
+ }
+ if (outputIndex != result.length) {
+ IFileInfo[] tmp = new IFileInfo[outputIndex];
+ System.arraycopy(result, 0, tmp, 0, outputIndex);
+ result = tmp;
+ }
+ return result;
+ }
+ return list;
+ }
+
+ public static IFileInfo[] filterExcludes(IProject project, LinkedList filters, IContainer parent, IFileInfo[] list) throws CoreException {
+ if (filters.size() > 0) {
+ IFileInfo[] result = new IFileInfo[list.length];
+ int outputIndex = 0;
+
+ for (int i = 0; i < list.length; i++) {
+ IFileInfo info = list[i];
+ Iterator objIt = filters.iterator();
+ boolean shouldBeExcluded = false;
+ while (objIt.hasNext()) {
+ Filter filter = objIt.next();
+ if (filter.appliesTo(info)) {
+ if (filter.match(parent, info)) {
+ shouldBeExcluded = true;
+ break;
+ }
+ }
+ }
+ if (!shouldBeExcluded)
+ result[outputIndex++] = info;
+ }
+ if (outputIndex != result.length) {
+ IFileInfo[] tmp = new IFileInfo[outputIndex];
+ System.arraycopy(result, 0, tmp, 0, outputIndex);
+ result = tmp;
+ }
+ return result;
+ }
+ return list;
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/FilterDescription.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/FilterDescription.java
new file mode 100644
index 0000000000..592df91455
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/FilterDescription.java
@@ -0,0 +1,143 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2015 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Serge Beauchamp (Freescale Semiconductor) - initial API and implementation
+ * IBM Corporation - ongoing implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.resources;
+
+import java.util.LinkedList;
+import org.eclipse.core.resources.*;
+import org.eclipse.core.runtime.*;
+
+/**
+ * Class for describing the characteristics of filters that are stored
+ * in the project description.
+ */
+public class FilterDescription implements IResourceFilterDescription, Comparable {
+
+ private long id;
+
+ /**
+ * The resource type (IResourceFilter.INCLUDE_ONLY or IResourceFilter.EXCLUDE_ALL) and/or IResourceFilter.INHERITABLE
+ */
+ private int type;
+
+ private FileInfoMatcherDescription matcherDescription;
+
+ /**
+ * The resource that this filter is applied to
+ */
+ private IResource resource;
+
+ public FilterDescription() {
+ this.type = -1;
+ }
+
+ public FilterDescription(IResource resource, int type, FileInfoMatcherDescription matcherDescription) {
+ super();
+ Assert.isNotNull(resource);
+ this.type = type;
+ this.matcherDescription = matcherDescription;
+ this.resource = resource;
+ }
+
+ public boolean isInheritable() {
+ return (getType() & IResourceFilterDescription.INHERITABLE) != 0;
+ }
+
+ public static LinkedList copy(LinkedList originalDescriptions, IResource resource) {
+ LinkedList copy = new LinkedList();
+ for (FilterDescription desc : originalDescriptions) {
+ FilterDescription newDesc = new FilterDescription(resource, desc.getType(), desc.getFileInfoMatcherDescription());
+ copy.add(newDesc);
+ }
+ return copy;
+ }
+
+ public long getId() {
+ return id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ @Override
+ public int getType() {
+ return type;
+ }
+
+ public void setType(int type) {
+ this.type = type;
+ }
+
+ public void setResource(IResource resource) {
+ this.resource = resource;
+ }
+
+ @Override
+ public IResource getResource() {
+ return resource;
+ }
+
+ @Override
+ public FileInfoMatcherDescription getFileInfoMatcherDescription() {
+ return matcherDescription;
+ }
+
+ public void setFileInfoMatcherDescription(FileInfoMatcherDescription matcherDescription) {
+ this.matcherDescription = matcherDescription;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + (int) (id ^ (id >>> 32));
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ FilterDescription other = (FilterDescription) obj;
+ if (id != other.id)
+ return false;
+ return true;
+ }
+
+ /**
+ * Compare filter descriptions in a way that sorts them topologically by path.
+ */
+ @Override
+ public int compareTo(FilterDescription that) {
+ IPath path1 = this.getResource().getProjectRelativePath();
+ IPath path2 = that.getResource().getProjectRelativePath();
+ int count1 = path1.segmentCount();
+ int compare = count1 - path2.segmentCount();
+ if (compare != 0)
+ return compare;
+ for (int i = 0; i < count1; i++) {
+ compare = path1.segment(i).compareTo(path2.segment(i));
+ if (compare != 0)
+ return compare;
+ }
+ return 0;
+ }
+
+ @Override
+ public void delete(int updateFlags, IProgressMonitor monitor) throws CoreException {
+ ((Container) getResource()).removeFilter(this, updateFlags, monitor);
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/FilterDescriptor.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/FilterDescriptor.java
new file mode 100644
index 0000000000..be0d0ebd81
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/FilterDescriptor.java
@@ -0,0 +1,95 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2015 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.resources;
+
+import org.eclipse.core.internal.utils.Policy;
+import org.eclipse.core.resources.IFilterMatcherDescriptor;
+import org.eclipse.core.resources.filtermatchers.AbstractFileInfoMatcher;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IConfigurationElement;
+
+public class FilterDescriptor implements IFilterMatcherDescriptor {
+ private String id;
+ private String name;
+ private String description;
+ private String argumentType;
+ private boolean isFirst = false;
+ private IConfigurationElement element;
+
+ public FilterDescriptor(IConfigurationElement element) {
+ this(element, true);
+ }
+
+ public FilterDescriptor(IConfigurationElement element, boolean instantiateFactory) {
+ id = element.getAttribute("id"); //$NON-NLS-1$
+ name = element.getAttribute("name"); //$NON-NLS-1$
+ description = element.getAttribute("description"); //$NON-NLS-1$
+ argumentType = element.getAttribute("argumentType"); //$NON-NLS-1$
+ if (argumentType == null)
+ argumentType = IFilterMatcherDescriptor.ARGUMENT_TYPE_NONE;
+ this.element = element;
+ String ordering = element.getAttribute("ordering"); //$NON-NLS-1$
+ if (ordering != null)
+ isFirst = ordering.equals("first"); //$NON-NLS-1$
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.resources.IFilterDescriptor#getId()
+ */
+ @Override
+ public String getId() {
+ return id;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.resources.IFilterDescriptor#getName()
+ */
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.resources.IFilterDescriptor#getDescription()
+ */
+ @Override
+ public String getDescription() {
+ return description;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.resources.IFilterDescriptor#getArgumentType()
+ */
+ @Override
+ public String getArgumentType() {
+ return argumentType;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.resources.IFilterDescriptor#getFactory()
+ */
+ public AbstractFileInfoMatcher createFilter() {
+ try {
+ return (AbstractFileInfoMatcher) element.createExecutableExtension("class"); //$NON-NLS-1$
+ } catch (CoreException e) {
+ Policy.log(e);
+ return null;
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.resources.IFilterDescriptor#isFirstOrdering()
+ */
+ @Override
+ public boolean isFirstOrdering() {
+ return isFirst;
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/FilterTypeManager.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/FilterTypeManager.java
new file mode 100644
index 0000000000..bd4e71f855
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/FilterTypeManager.java
@@ -0,0 +1,105 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2015 Freescale Semiconductor and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Serge Beauchamp (Freescale Semiconductor) - initial API and implementation
+ * IBM Corporation - ongoing development
+ * James Blackburn (Broadcom Corp.) - ongoing development
+ *******************************************************************************/
+package org.eclipse.core.internal.resources;
+
+import java.util.HashMap;
+import org.eclipse.core.resources.IFilterMatcherDescriptor;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.resources.filtermatchers.AbstractFileInfoMatcher;
+import org.eclipse.core.runtime.*;
+
+/**
+ * This class collects all the registered {@link AbstractFileInfoMatcher} instances along
+ * with their properties.
+ */
+class FilterTypeManager implements IManager {
+
+ private static final String FILTER_ELEMENT = "filterMatcher"; //$NON-NLS-1$
+
+ private HashMap factories = new HashMap();
+
+ public FilterTypeManager() {
+ IExtensionPoint point = RegistryFactory.getRegistry().getExtensionPoint(ResourcesPlugin.PI_RESOURCES, ResourcesPlugin.PT_FILTER_MATCHERS);
+ if (point != null) {
+ IExtension[] ext = point.getExtensions();
+ // initial population
+ for (int i = 0; i < ext.length; i++) {
+ IExtension extension = ext[i];
+ processExtension(extension);
+ }
+ RegistryFactory.getRegistry().addListener(new IRegistryEventListener() {
+ @Override
+ public void added(IExtension[] extensions) {
+ for (int i = 0; i < extensions.length; i++)
+ processExtension(extensions[i]);
+ }
+
+ @Override
+ public void added(IExtensionPoint[] extensionPoints) {
+ // nothing to do
+ }
+
+ @Override
+ public void removed(IExtension[] extensions) {
+ for (int i = 0; i < extensions.length; i++)
+ processRemovedExtension(extensions[i]);
+ }
+
+ @Override
+ public void removed(IExtensionPoint[] extensionPoints) {
+ // nothing to do
+ }
+ });
+ }
+ }
+
+ public IFilterMatcherDescriptor getFilterDescriptor(String id) {
+ return factories.get(id);
+ }
+
+ public IFilterMatcherDescriptor[] getFilterDescriptors() {
+ return factories.values().toArray(new IFilterMatcherDescriptor[0]);
+ }
+
+ protected void processExtension(IExtension extension) {
+ IConfigurationElement[] elements = extension.getConfigurationElements();
+ for (int i = 0; i < elements.length; i++) {
+ IConfigurationElement element = elements[i];
+ if (element.getName().equalsIgnoreCase(FILTER_ELEMENT)) {
+ IFilterMatcherDescriptor desc = new FilterDescriptor(element);
+ factories.put(desc.getId(), desc);
+ }
+ }
+ }
+
+ protected void processRemovedExtension(IExtension extension) {
+ IConfigurationElement[] elements = extension.getConfigurationElements();
+ for (int i = 0; i < elements.length; i++) {
+ IConfigurationElement element = elements[i];
+ if (element.getName().equalsIgnoreCase(FILTER_ELEMENT)) {
+ IFilterMatcherDescriptor desc = new FilterDescriptor(element, false);
+ factories.remove(desc.getId());
+ }
+ }
+ }
+
+ @Override
+ public void shutdown(IProgressMonitor monitor) {
+ //nothing to do
+ }
+
+ @Override
+ public void startup(IProgressMonitor monitor) {
+ //nothing to do
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Folder.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Folder.java
new file mode 100644
index 0000000000..061a84fb61
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Folder.java
@@ -0,0 +1,186 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2015 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * Serge Beauchamp (Freescale Semiconductor) - [229633] Group and Project Path Variable Support
+ *******************************************************************************/
+package org.eclipse.core.internal.resources;
+
+import java.net.URI;
+import org.eclipse.core.filesystem.IFileInfo;
+import org.eclipse.core.filesystem.IFileStore;
+import org.eclipse.core.internal.utils.Messages;
+import org.eclipse.core.internal.utils.Policy;
+import org.eclipse.core.resources.*;
+import org.eclipse.core.runtime.*;
+import org.eclipse.core.runtime.jobs.ISchedulingRule;
+import org.eclipse.osgi.util.NLS;
+
+public class Folder extends Container implements IFolder {
+ protected Folder(IPath path, Workspace container) {
+ super(path, container);
+ }
+
+ protected void assertCreateRequirements(IFileStore store, IFileInfo localInfo, int updateFlags) throws CoreException {
+ checkDoesNotExist();
+ Container parent = (Container) getParent();
+ ResourceInfo info = parent.getResourceInfo(false, false);
+ parent.checkAccessible(getFlags(info));
+ checkValidGroupContainer(parent, false, false);
+
+ final boolean force = (updateFlags & IResource.FORCE) != 0;
+ if (!force && localInfo.exists()) {
+ //return an appropriate error message for case variant collisions
+ if (!Workspace.caseSensitive) {
+ String name = getLocalManager().getLocalName(store);
+ if (name != null && !store.getName().equals(name)) {
+ String msg = NLS.bind(Messages.resources_existsLocalDifferentCase, new Path(store.toString()).removeLastSegments(1).append(name).toOSString());
+ throw new ResourceException(IResourceStatus.CASE_VARIANT_EXISTS, getFullPath(), msg, null);
+ }
+ }
+ String msg = NLS.bind(Messages.resources_fileExists, store.toString());
+ throw new ResourceException(IResourceStatus.FAILED_WRITE_LOCAL, getFullPath(), msg, null);
+ }
+ }
+
+ /* (non-Javadoc)
+ * Changes this folder to be a file in the resource tree and returns the newly
+ * created file. All related properties are deleted. It is assumed that on
+ * disk the resource is already a file so no action is taken to delete the disk
+ * contents.
+ *
+ * This method is for the exclusive use of the local refresh mechanism
+ *
+ * @see org.eclipse.core.internal.localstore.RefreshLocalVisitor#folderToFile(UnifiedTreeNode, Resource)
+ */
+ public IFile changeToFile() throws CoreException {
+ getPropertyManager().deleteProperties(this, IResource.DEPTH_INFINITE);
+ IFile result = workspace.getRoot().getFile(path);
+ if (isLinked()) {
+ URI location = getRawLocationURI();
+ delete(IResource.NONE, null);
+ result.createLink(location, IResource.ALLOW_MISSING_LOCAL, null);
+ } else {
+ workspace.deleteResource(this);
+ workspace.createResource(result, false);
+ }
+ return result;
+ }
+
+ @Override
+ public void create(int updateFlags, boolean local, IProgressMonitor monitor) throws CoreException {
+ if ((updateFlags & IResource.VIRTUAL) == IResource.VIRTUAL) {
+ createLink(LinkDescription.VIRTUAL_LOCATION, updateFlags, monitor);
+ return;
+ }
+
+ final boolean force = (updateFlags & IResource.FORCE) != 0;
+ monitor = Policy.monitorFor(monitor);
+ try {
+ String message = NLS.bind(Messages.resources_creating, getFullPath());
+ monitor.beginTask(message, Policy.totalWork);
+ checkValidPath(path, FOLDER, true);
+ final ISchedulingRule rule = workspace.getRuleFactory().createRule(this);
+ try {
+ workspace.prepareOperation(rule, monitor);
+ IFileStore store = getStore();
+ IFileInfo localInfo = store.fetchInfo();
+ assertCreateRequirements(store, localInfo, updateFlags);
+ workspace.beginOperation(true);
+ if (force && !Workspace.caseSensitive && localInfo.exists()) {
+ String name = getLocalManager().getLocalName(store);
+ if (name == null || localInfo.getName().equals(name)) {
+ delete(true, null);
+ } else {
+ // The file system is not case sensitive and a case variant exists at this location
+ String msg = NLS.bind(Messages.resources_existsLocalDifferentCase, new Path(store.toString()).removeLastSegments(1).append(name).toOSString());
+ throw new ResourceException(IResourceStatus.CASE_VARIANT_EXISTS, getFullPath(), msg, null);
+ }
+ }
+ internalCreate(updateFlags, local, Policy.subMonitorFor(monitor, Policy.opWork));
+ workspace.getAliasManager().updateAliases(this, getStore(), IResource.DEPTH_ZERO, monitor);
+ } catch (OperationCanceledException e) {
+ workspace.getWorkManager().operationCanceled();
+ throw e;
+ } finally {
+ workspace.endOperation(rule, true, Policy.subMonitorFor(monitor, Policy.endOpWork));
+ }
+ } finally {
+ monitor.done();
+ }
+ }
+
+ @Override
+ public void create(boolean force, boolean local, IProgressMonitor monitor) throws CoreException {
+ // funnel all operations to central method
+ create((force ? IResource.FORCE : IResource.NONE), local, monitor);
+ }
+
+ /**
+ * Ensures that this folder exists in the workspace. This is similar in
+ * concept to mkdirs but it does not work on projects.
+ * If this folder is created, it will be marked as being local.
+ */
+ public void ensureExists(IProgressMonitor monitor) throws CoreException {
+ ResourceInfo info = getResourceInfo(false, false);
+ int flags = getFlags(info);
+ if (exists(flags, true))
+ return;
+ if (exists(flags, false)) {
+ String message = NLS.bind(Messages.resources_folderOverFile, getFullPath());
+ throw new ResourceException(IResourceStatus.RESOURCE_WRONG_TYPE, getFullPath(), message, null);
+ }
+ Container parent = (Container) getParent();
+ if (parent.getType() == PROJECT) {
+ info = parent.getResourceInfo(false, false);
+ parent.checkExists(getFlags(info), true);
+ } else
+ ((Folder) parent).ensureExists(monitor);
+ if (getType() == FOLDER && isUnderVirtual())
+ create(IResource.VIRTUAL | IResource.FORCE, true, monitor);
+ else
+ internalCreate(IResource.FORCE, true, monitor);
+ }
+
+ @Override
+ public String getDefaultCharset(boolean checkImplicit) {
+ // non-existing resources default to parent's charset
+ if (!exists())
+ return checkImplicit ? workspace.getCharsetManager().getCharsetFor(getFullPath().removeLastSegments(1), true) : null;
+ return workspace.getCharsetManager().getCharsetFor(getFullPath(), checkImplicit);
+ }
+
+ @Override
+ public int getType() {
+ return FOLDER;
+ }
+
+ public void internalCreate(int updateFlags, boolean local, IProgressMonitor monitor) throws CoreException {
+ monitor = Policy.monitorFor(monitor);
+ try {
+ String message = NLS.bind(Messages.resources_creating, getFullPath());
+ monitor.beginTask(message, Policy.totalWork);
+ workspace.createResource(this, updateFlags);
+ if (local) {
+ try {
+ final boolean force = (updateFlags & IResource.FORCE) != 0;
+ getLocalManager().write(this, force, Policy.subMonitorFor(monitor, Policy.totalWork));
+ } catch (CoreException e) {
+ // a problem happened creating the folder on disk, so delete from the workspace
+ workspace.deleteResource(this);
+ throw e; // rethrow
+ }
+ }
+ internalSetLocal(local, DEPTH_ZERO);
+ if (!local)
+ getResourceInfo(true, true).clearModificationStamp();
+ } finally {
+ monitor.done();
+ }
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ICoreConstants.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ICoreConstants.java
new file mode 100644
index 0000000000..98518caf5b
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ICoreConstants.java
@@ -0,0 +1,125 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * Serge Beauchamp (Freescale Semiconductor) - [229633] Group Support
+ * Broadcom Corporation - build configurations
+ *******************************************************************************/
+package org.eclipse.core.internal.resources;
+
+import org.eclipse.core.resources.*;
+import org.eclipse.core.runtime.QualifiedName;
+
+public interface ICoreConstants {
+
+ // Standard resource properties
+ /** map of builders to their last built state. */
+ public static final QualifiedName K_BUILD_LIST = new QualifiedName(ResourcesPlugin.PI_RESOURCES, "BuildMap"); //$NON-NLS-1$
+
+ /**
+ * Command line argument indicating a workspace refresh on startup is requested.
+ */
+ public static final String REFRESH_ON_STARTUP = "-refresh"; //$NON-NLS-1$
+
+ // resource info constants
+ static final long I_NULL_SYNC_INFO = -1;
+
+ // Useful flag masks for resource info states
+ static final int M_OPEN = 0x1;
+ static final int M_LOCAL_EXISTS = 0x2;
+ static final int M_PHANTOM = 0x8;
+ static final int M_USED = 0x10;
+ static final int M_TYPE = 0xF00;
+ static final int M_TYPE_START = 8;
+ static final int M_MARKERS_SNAP_DIRTY = 0x1000;
+ static final int M_SYNCINFO_SNAP_DIRTY = 0x2000;
+ /**
+ * Marks this resource as derived.
+ * @since 2.0
+ */
+ static final int M_DERIVED = 0x4000;
+ /**
+ * Marks this resource as a team-private member of its container.
+ * @since 2.0
+ */
+ static final int M_TEAM_PRIVATE_MEMBER = 0x8000;
+ /**
+ * Marks this resource as a hidden resource.
+ * @since 3.4
+ */
+ static final int M_HIDDEN = 0x200000;
+
+ /**
+ * Marks this resource as a linked resource.
+ * @since 2.1
+ */
+ static final int M_LINK = 0x10000;
+ /**
+ * Marks this resource as virtual.
+ * @since 3.6
+ */
+ static final int M_VIRTUAL = 0x80000;
+ /**
+ * The file has no content description.
+ * @since 3.0
+ */
+ static final int M_NO_CONTENT_DESCRIPTION = 0x20000;
+ /**
+ * The file has a default content description.
+ * @since 3.0
+ */
+ static final int M_DEFAULT_CONTENT_DESCRIPTION = 0x40000;
+
+ /**
+ * Marks this resource as having undiscovered children
+ * @since 3.1
+ */
+ static final int M_CHILDREN_UNKNOWN = 0x100000;
+
+ /**
+ * Set of flags that should be cleared when the contents for a file change.
+ * @since 3.0
+ */
+ static final int M_CONTENT_CACHE = M_NO_CONTENT_DESCRIPTION | M_DEFAULT_CONTENT_DESCRIPTION;
+
+ static final int NULL_FLAG = -1;
+
+ /**
+ * A private preference stored in a preference node to indicate the preference
+ * version that is used. This version identifier is used to handle preference
+ * migration when old preferences are loaded.
+ */
+ public static final String PREF_VERSION_KEY = "version"; //$NON-NLS-1$
+
+ /**
+ * A private preference stored in a preference node to indicate the preference
+ * version that is used. This version identifier is used to handle preference
+ * migration when old preferences are loaded.
+ */
+ public static final String PREF_VERSION = "1"; //$NON-NLS-1$
+
+ // Internal status codes
+ // Information Only [00-24]
+ // Warnings [25-74]
+ public static final int CRASH_DETECTED = 10035;
+
+ // Errors [75-99]
+
+ public static final int PROJECT_SEGMENT_LENGTH = 1;
+ public static final int MINIMUM_FOLDER_SEGMENT_LENGTH = 2;
+ public static final int MINIMUM_FILE_SEGMENT_LENGTH = 2;
+
+ public static final int WORKSPACE_TREE_VERSION_1 = 67305985;
+ public static final int WORKSPACE_TREE_VERSION_2 = 67305986;
+
+ // helper constants for empty structures
+ public static final IBuildConfiguration[] EMPTY_BUILD_CONFIG_ARRAY = new IBuildConfiguration[0];
+ public static final IProject[] EMPTY_PROJECT_ARRAY = new IProject[0];
+ public static final IResource[] EMPTY_RESOURCE_ARRAY = new IResource[0];
+ public static final IFileState[] EMPTY_FILE_STATES = new IFileState[0];
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/IManager.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/IManager.java
new file mode 100644
index 0000000000..553e48e24e
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/IManager.java
@@ -0,0 +1,20 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2005 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.resources;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+
+public interface IManager {
+ public void shutdown(IProgressMonitor monitor) throws CoreException;
+
+ public void startup(IProgressMonitor monitor) throws CoreException;
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/IMarkerSetElement.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/IMarkerSetElement.java
new file mode 100644
index 0000000000..0f77305620
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/IMarkerSetElement.java
@@ -0,0 +1,15 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2005 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.resources;
+
+public interface IMarkerSetElement {
+ public long getId();
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/IModelObjectConstants.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/IModelObjectConstants.java
new file mode 100644
index 0000000000..4a258e9cee
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/IModelObjectConstants.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * Serge Beauchamp (Freescale Semiconductor) - [252996] add resource filtering
+ * Serge Beauchamp (Freescale Semiconductor) - [229633] Project Path Variable Support
+ * Markus Schorn (Wind River) - [306575] Save snapshot location with project
+ *******************************************************************************/
+package org.eclipse.core.internal.resources;
+
+public interface IModelObjectConstants {
+ public static final String ARGUMENTS = "arguments"; //$NON-NLS-1$
+ public static final String ID = "id"; //$NON-NLS-1$
+ public static final String AUTOBUILD = "autobuild"; //$NON-NLS-1$
+ public static final String BUILD_COMMAND = "buildCommand"; //$NON-NLS-1$
+ public static final String BUILD_ORDER = "buildOrder"; //$NON-NLS-1$
+ public static final String BUILD_SPEC = "buildSpec"; //$NON-NLS-1$
+ public static final String BUILD_TRIGGERS = "triggers"; //$NON-NLS-1$
+ public static final String TRIGGER_AUTO = "auto"; //$NON-NLS-1$
+ public static final String TRIGGER_CLEAN = "clean"; //$NON-NLS-1$
+ public static final String TRIGGER_FULL = "full"; //$NON-NLS-1$
+ public static final String TRIGGER_INCREMENTAL = "incremental"; //$NON-NLS-1$
+ public static final String COMMENT = "comment"; //$NON-NLS-1$
+ public static final String DICTIONARY = "dictionary"; //$NON-NLS-1$
+ public static final String KEY = "key"; //$NON-NLS-1$
+ public static final String LOCATION = "location"; //$NON-NLS-1$
+ public static final String LOCATION_URI = "locationURI"; //$NON-NLS-1$
+ public static final String APPLY_FILE_STATE_POLICY = "applyFileStatePolicy"; //$NON-NLS-1$
+ public static final String FILE_STATE_LONGEVITY = "fileStateLongevity"; //$NON-NLS-1$
+ public static final String MAX_FILE_STATE_SIZE = "maxFileStateSize"; //$NON-NLS-1$
+ public static final String MAX_FILE_STATES = "maxFileStates"; //$NON-NLS-1$
+ /**
+ * The project relative path is called the link name for backwards compatibility
+ */
+ public static final String NAME = "name"; //$NON-NLS-1$
+ public static final String NATURE = "nature"; //$NON-NLS-1$
+ public static final String NATURES = "natures"; //$NON-NLS-1$
+ public static final String SNAPSHOT_INTERVAL = "snapshotInterval"; //$NON-NLS-1$
+ public static final String PROJECT = "project"; //$NON-NLS-1$
+ public static final String PROJECT_DESCRIPTION = "projectDescription"; //$NON-NLS-1$
+ public static final String PROJECTS = "projects"; //$NON-NLS-1$
+ public static final String TYPE = "type"; //$NON-NLS-1$
+ public static final String VALUE = "value"; //$NON-NLS-1$
+ public static final String WORKSPACE_DESCRIPTION = "workspaceDescription"; //$NON-NLS-1$
+ public static final String LINKED_RESOURCES = "linkedResources"; //$NON-NLS-1$
+ public static final String LINK = "link"; //$NON-NLS-1$
+ public static final String FILTERED_RESOURCES = "filteredResources"; //$NON-NLS-1$
+ public static final String FILTER = "filter"; //$NON-NLS-1$
+ public static final String MATCHER = "matcher"; //$NON-NLS-1$
+ public static final String VARIABLE = "variable"; //$NON-NLS-1$
+ public static final String VARIABLE_LIST = "variableList"; //$NON-NLS-1$
+ public static final String SNAPSHOT_LOCATION = "snapshotLocation"; //$NON-NLS-1$
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/InternalTeamHook.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/InternalTeamHook.java
new file mode 100644
index 0000000000..44b07d9418
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/InternalTeamHook.java
@@ -0,0 +1,32 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2005 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM - Initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.resources;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResourceRuleFactory;
+import org.eclipse.core.resources.team.TeamHook;
+
+/**
+ * The internal abstract superclass of all TeamHook implementations. This superclass
+ * provides access to internal non-API methods that are not available from the API
+ * package. Plugin developers should not subclass this class.
+ *
+ * @see TeamHook
+ */
+public class InternalTeamHook {
+ /* (non-Javadoc)
+ * Internal implementation of TeamHook#setRulesFor(IProject,IResourceRuleFactory)
+ */
+ protected void setRuleFactory(IProject project, IResourceRuleFactory factory) {
+ Workspace workspace = ((Workspace) project.getWorkspace());
+ ((Rules) workspace.getRuleFactory()).setRuleFactory(project, factory);
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/InternalWorkspaceJob.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/InternalWorkspaceJob.java
new file mode 100644
index 0000000000..cf5dede10b
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/InternalWorkspaceJob.java
@@ -0,0 +1,54 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2014 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM - Initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.resources;
+
+import org.eclipse.core.internal.utils.Policy;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.*;
+import org.eclipse.core.runtime.jobs.Job;
+
+/**
+ * Batches the activity of a job as a single operation, without obtaining the workspace
+ * lock.
+ */
+public abstract class InternalWorkspaceJob extends Job {
+ private Workspace workspace;
+
+ public InternalWorkspaceJob(String name) {
+ super(name);
+ this.workspace = (Workspace) ResourcesPlugin.getWorkspace();
+ }
+
+ @Override
+ public final IStatus run(IProgressMonitor monitor) {
+ monitor = Policy.monitorFor(monitor);
+ try {
+ int depth = -1;
+ try {
+ workspace.prepareOperation(null, monitor);
+ workspace.beginOperation(true);
+ depth = workspace.getWorkManager().beginUnprotected();
+ return runInWorkspace(monitor);
+ } catch (OperationCanceledException e) {
+ workspace.getWorkManager().operationCanceled();
+ return Status.CANCEL_STATUS;
+ } finally {
+ if (depth >= 0)
+ workspace.getWorkManager().endUnprotected(depth);
+ workspace.endOperation(null, false, monitor);
+ }
+ } catch (CoreException e) {
+ return e.getStatus();
+ }
+ }
+
+ protected abstract IStatus runInWorkspace(IProgressMonitor monitor) throws CoreException;
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/LinkDescription.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/LinkDescription.java
new file mode 100644
index 0000000000..3a3560adbd
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/LinkDescription.java
@@ -0,0 +1,126 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2015 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * Serge Beauchamp (Freescale Semiconductor) - [229633] Group and Project Path Variable Support
+ * James Blackburn (Broadcom Corp.) - ongoing development
+ *******************************************************************************/
+package org.eclipse.core.internal.resources;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.*;
+
+/**
+ * Object for describing the characteristics of linked resources that are stored
+ * in the project description.
+ */
+public class LinkDescription implements Comparable {
+ public static final URI VIRTUAL_LOCATION = getVirtualLocation();
+
+ private static URI getVirtualLocation() {
+ try {
+ return new URI("virtual:/virtual"); //$NON-NLS-1$
+ } catch (URISyntaxException e) {
+ //cannot happen
+ return null;
+ }
+ }
+
+ private URI localLocation;
+
+ /**
+ * The project relative path.
+ */
+ private IPath path;
+ /**
+ * The resource type (IResource.FILE or IResoruce.FOLDER)
+ */
+ private int type;
+
+ public LinkDescription() {
+ this.path = Path.EMPTY;
+ this.type = -1;
+ }
+
+ public LinkDescription(IResource linkedResource, URI location) {
+ super();
+ Assert.isNotNull(linkedResource);
+ Assert.isNotNull(location);
+ this.type = linkedResource.getType();
+ this.path = linkedResource.getProjectRelativePath();
+ this.localLocation = location;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o.getClass() == LinkDescription.class))
+ return false;
+ LinkDescription other = (LinkDescription) o;
+ return localLocation.equals(other.localLocation) && path.equals(other.path) && type == other.type;
+ }
+
+ public URI getLocationURI() {
+ return localLocation;
+ }
+
+ /**
+ * Returns the project relative path of the resource that is linked.
+ * @return the project relative path of the resource that is linked.
+ */
+ public IPath getProjectRelativePath() {
+ return path;
+ }
+
+ public int getType() {
+ return type;
+ }
+
+ public boolean isGroup() {
+ return localLocation.equals(VIRTUAL_LOCATION);
+ }
+
+ @Override
+ public int hashCode() {
+ return type + path.hashCode() + localLocation.hashCode();
+ }
+
+ public void setLocationURI(URI location) {
+ this.localLocation = location;
+ }
+
+ public void setPath(IPath path) {
+ this.path = path;
+ }
+
+ public void setType(int type) {
+ this.type = type;
+ }
+
+ /**
+ * Compare link descriptions in a way that sorts them topologically by path.
+ * This is important to ensure we process links in topological (breadth-first) order when reconciling
+ * links. See {@link Project#reconcileLinksAndGroups(ProjectDescription)}.
+ */
+ @Override
+ public int compareTo(LinkDescription that) {
+ IPath path1 = this.getProjectRelativePath();
+ IPath path2 = that.getProjectRelativePath();
+ int count1 = path1.segmentCount();
+ int compare = count1 - path2.segmentCount();
+ if (compare != 0)
+ return compare;
+ for (int i = 0; i < count1; i++) {
+ compare = path1.segment(i).compareTo(path2.segment(i));
+ if (compare != 0)
+ return compare;
+ }
+ return 0;
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/LocalMetaArea.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/LocalMetaArea.java
new file mode 100644
index 0000000000..a2993df653
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/LocalMetaArea.java
@@ -0,0 +1,512 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2014 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * Francis Lynch (Wind River) - [301563] Save and load tree snapshots
+ * Broadcom Corporation - ongoing development
+ * Sergey Prigogin (Google) - [437005] Out-of-date .snap file prevents Eclipse from running
+ *******************************************************************************/
+package org.eclipse.core.internal.resources;
+
+import java.io.*;
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+import org.eclipse.core.filesystem.URIUtil;
+import org.eclipse.core.internal.localstore.SafeChunkyInputStream;
+import org.eclipse.core.internal.localstore.SafeChunkyOutputStream;
+import org.eclipse.core.internal.utils.*;
+import org.eclipse.core.resources.*;
+import org.eclipse.core.runtime.*;
+import org.eclipse.osgi.util.NLS;
+
+public class LocalMetaArea implements ICoreConstants {
+ /* package */static final String F_BACKUP_FILE_EXTENSION = ".bak"; //$NON-NLS-1$
+ /* package */static final String F_DESCRIPTION = ".workspace"; //$NON-NLS-1$
+
+ /* package */static final String F_HISTORY_STORE = ".history"; //$NON-NLS-1$
+ /* package */static final String F_MARKERS = ".markers"; //$NON-NLS-1$
+ /* package */static final String F_OLD_PROJECT = ".prj"; //$NON-NLS-1$
+ /* package */static final String F_PROJECT_LOCATION = ".location"; //$NON-NLS-1$
+ /* package */static final String F_PROJECTS = ".projects"; //$NON-NLS-1$
+ /* package */static final String F_PROPERTIES = ".properties"; //$NON-NLS-1$
+ /* package */static final String F_REFRESH = ".refresh"; //$NON-NLS-1$
+ /* package */static final String F_ROOT = ".root"; //$NON-NLS-1$
+ /* package */static final String F_SAFE_TABLE = ".safetable"; //$NON-NLS-1$
+ /* package */static final String F_SNAP = ".snap"; //$NON-NLS-1$
+ /* package */static final String F_SNAP_EXTENSION = "snap"; //$NON-NLS-1$
+ /* package */static final String F_SYNCINFO = ".syncinfo"; //$NON-NLS-1$
+ /* package */static final String F_TREE = ".tree"; //$NON-NLS-1$
+ /* package */static final String URI_PREFIX = "URI//"; //$NON-NLS-1$
+ /* package */static final String F_METADATA = ".metadata"; //$NON-NLS-1$
+
+ protected final IPath metaAreaLocation;
+
+ /**
+ * The project location is just stored as an optimization, to avoid recomputing it.
+ */
+ protected final IPath projectMetaLocation;
+
+ public LocalMetaArea() {
+ super();
+ metaAreaLocation = ResourcesPlugin.getPlugin().getStateLocation();
+ projectMetaLocation = metaAreaLocation.append(F_PROJECTS);
+ }
+
+ /**
+ * For backwards compatibility, if there is a project at the old project
+ * description location, delete it.
+ */
+ public void clearOldDescription(IProject target) {
+ Workspace.clear(getOldDescriptionLocationFor(target).toFile());
+ }
+
+ /**
+ * Delete the refresh snapshot once it has been used to open a new project.
+ */
+ public void clearRefresh(IProject target) {
+ Workspace.clear(getRefreshLocationFor(target).toFile());
+ }
+
+ public void create(IProject target) {
+ java.io.File file = locationFor(target).toFile();
+ //make sure area is empty
+ Workspace.clear(file);
+ file.mkdirs();
+ }
+
+ /**
+ * Creates the meta area root directory.
+ */
+ public synchronized void createMetaArea() throws CoreException {
+ java.io.File workspaceLocation = metaAreaLocation.toFile();
+ Workspace.clear(workspaceLocation);
+ if (!workspaceLocation.mkdirs()) {
+ String message = NLS.bind(Messages.resources_writeWorkspaceMeta, workspaceLocation);
+ throw new ResourceException(IResourceStatus.FAILED_WRITE_METADATA, null, message, null);
+ }
+ }
+
+ /**
+ * The project is being deleted. Delete all meta-data associated with the
+ * project.
+ */
+ public void delete(IProject target) throws CoreException {
+ IPath path = locationFor(target);
+ if (!Workspace.clear(path.toFile()) && path.toFile().exists()) {
+ String message = NLS.bind(Messages.resources_deleteMeta, target.getFullPath());
+ throw new ResourceException(IResourceStatus.FAILED_DELETE_METADATA, target.getFullPath(), message, null);
+ }
+ }
+
+ public IPath getBackupLocationFor(IPath file) {
+ return file.removeLastSegments(1).append(file.lastSegment() + F_BACKUP_FILE_EXTENSION);
+ }
+
+ public IPath getHistoryStoreLocation() {
+ return metaAreaLocation.append(F_HISTORY_STORE);
+ }
+
+ /**
+ * Returns the local file system location which contains the META data for
+ * the resources plugin (i.e., the entire workspace).
+ */
+ public IPath getLocation() {
+ return metaAreaLocation;
+ }
+
+ /**
+ * Returns the path of the file in which to save markers for the given
+ * resource. Should only be called for the workspace root and projects.
+ */
+ public IPath getMarkersLocationFor(IResource resource) {
+ Assert.isNotNull(resource);
+ Assert.isLegal(resource.getType() == IResource.ROOT || resource.getType() == IResource.PROJECT);
+ return locationFor(resource).append(F_MARKERS);
+ }
+
+ /**
+ * Returns the path of the file in which to snapshot markers for the given
+ * resource. Should only be called for the workspace root and projects.
+ */
+ public IPath getMarkersSnapshotLocationFor(IResource resource) {
+ return getMarkersLocationFor(resource).addFileExtension(F_SNAP_EXTENSION);
+ }
+
+ /**
+ * The project description file is the only metadata file stored outside
+ * the metadata area. It is stored as a file directly under the project
+ * location. For backwards compatibility, we also have to check for a
+ * project file at the old location in the metadata area.
+ */
+ public IPath getOldDescriptionLocationFor(IProject target) {
+ return locationFor(target).append(F_OLD_PROJECT);
+ }
+
+ public IPath getOldWorkspaceDescriptionLocation() {
+ return metaAreaLocation.append(F_DESCRIPTION);
+ }
+
+ public IPath getPropertyStoreLocation(IResource resource) {
+ int type = resource.getType();
+ Assert.isTrue(type != IResource.FILE && type != IResource.FOLDER);
+ return locationFor(resource).append(F_PROPERTIES);
+ }
+
+ /**
+ * Returns the path of the file in which to save the refresh snapshot for
+ * the given project.
+ */
+ public IPath getRefreshLocationFor(IProject project) {
+ Assert.isNotNull(project);
+ return locationFor(project).append(F_REFRESH);
+ }
+
+ public IPath getSafeTableLocationFor(String pluginId) {
+ IPath prefix = metaAreaLocation.append(F_SAFE_TABLE);
+ // if the plugin is the resources plugin, we return the master table
+ // location
+ if (pluginId.equals(ResourcesPlugin.PI_RESOURCES))
+ return prefix.append(pluginId); // master table
+ int saveNumber = getWorkspace().getSaveManager().getSaveNumber(pluginId);
+ return prefix.append(pluginId + "." + saveNumber); //$NON-NLS-1$
+ }
+
+ /**
+ * Returns the path of the snapshot file. The name of the file is composed from a sequence
+ * number corresponding to the sequence number of tree file and ".snap" extension. Should
+ * only be called for the workspace root.
+ */
+ public IPath getSnapshotLocationFor(IResource resource) {
+ Assert.isNotNull(resource);
+ Assert.isLegal(resource.getType() == IResource.ROOT);
+ IPath key = resource.getFullPath().append(F_TREE);
+ String sequenceNumber = getWorkspace().getSaveManager().getMasterTable().getProperty(key.toString());
+ if (sequenceNumber == null)
+ sequenceNumber = "0"; //$NON-NLS-1$
+ return metaAreaLocation.append(sequenceNumber + F_SNAP);
+ }
+
+ /**
+ * Returns the legacy, pre-4.4.1, path of the snapshot file. The name of the legacy snapshot
+ * file is ".snap". Should only be called for the workspace root.
+ */
+ public IPath getLegacySnapshotLocationFor(IResource resource) {
+ Assert.isNotNull(resource);
+ Assert.isLegal(resource.getType() == IResource.ROOT);
+ return metaAreaLocation.append(F_SNAP);
+ }
+
+ /**
+ * Returns the path of the file in which to save the sync information for
+ * the given resource. Should only be called for the workspace root and
+ * projects.
+ */
+ public IPath getSyncInfoLocationFor(IResource resource) {
+ Assert.isNotNull(resource);
+ Assert.isLegal(resource.getType() == IResource.ROOT || resource.getType() == IResource.PROJECT);
+ return locationFor(resource).append(F_SYNCINFO);
+ }
+
+ /**
+ * Returns the path of the file in which to snapshot the sync information
+ * for the given resource. Should only be called for the workspace root and
+ * projects.
+ */
+ public IPath getSyncInfoSnapshotLocationFor(IResource resource) {
+ return getSyncInfoLocationFor(resource).addFileExtension(F_SNAP_EXTENSION);
+ }
+
+ /**
+ * Returns the local file system location of the tree file for the given
+ * resource. This file does not follow the same save number as its plug-in.
+ * So, the number here is called "sequence number" and not "save number" to
+ * avoid confusion.
+ */
+ public IPath getTreeLocationFor(IResource target, boolean updateSequenceNumber) {
+ IPath key = target.getFullPath().append(F_TREE);
+ String sequenceNumber = getWorkspace().getSaveManager().getMasterTable().getProperty(key.toString());
+ if (sequenceNumber == null)
+ sequenceNumber = "0"; //$NON-NLS-1$
+ if (updateSequenceNumber) {
+ int n = new Integer(sequenceNumber).intValue() + 1;
+ n = n < 0 ? 1 : n;
+ sequenceNumber = new Integer(n).toString();
+ getWorkspace().getSaveManager().getMasterTable().setProperty(key.toString(), new Integer(sequenceNumber).toString());
+ }
+ return locationFor(target).append(sequenceNumber + F_TREE);
+ }
+
+ public IPath getWorkingLocation(IResource resource, String id) {
+ return locationFor(resource).append(id);
+ }
+
+ protected Workspace getWorkspace() {
+ return (Workspace) ResourcesPlugin.getWorkspace();
+ }
+
+ public boolean hasSavedProject(IProject project) {
+ //if there is a location file, then the project exists
+ return getOldDescriptionLocationFor(project).toFile().exists() || locationFor(project).append(F_PROJECT_LOCATION).toFile().exists();
+ }
+
+ public boolean hasSavedWorkspace() {
+ return metaAreaLocation.toFile().exists() || getBackupLocationFor(metaAreaLocation).toFile().exists();
+ }
+
+ /**
+ * Returns the local file system location in which the meta data for the
+ * resource with the given path is stored.
+ */
+ public IPath locationFor(IPath resourcePath) {
+ if (Path.ROOT.equals(resourcePath))
+ return metaAreaLocation.append(F_ROOT);
+ return projectMetaLocation.append(resourcePath.segment(0));
+ }
+
+ /**
+ * Returns the local file system location in which the meta data for the
+ * given resource is stored.
+ */
+ public IPath locationFor(IResource resource) {
+ if (resource.getType() == IResource.ROOT)
+ return metaAreaLocation.append(F_ROOT);
+ return projectMetaLocation.append(resource.getProject().getName());
+ }
+
+ /**
+ * Reads and returns the project description for the given project. Returns
+ * null if there was no project description file on disk. Throws an
+ * exception if there was any failure to read the project.
+ */
+ public ProjectDescription readOldDescription(IProject project) throws CoreException {
+ IPath path = getOldDescriptionLocationFor(project);
+ if (!path.toFile().exists())
+ return null;
+ IPath tempPath = getBackupLocationFor(path);
+ ProjectDescription description = null;
+ try {
+ description = new ProjectDescriptionReader(project).read(path, tempPath);
+ } catch (IOException e) {
+ String msg = NLS.bind(Messages.resources_readMeta, project.getName());
+ throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, project.getFullPath(), msg, e);
+ }
+ if (description == null) {
+ String msg = NLS.bind(Messages.resources_readMeta, project.getName());
+ throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, project.getFullPath(), msg, null);
+ }
+ return description;
+ }
+
+ /**
+ * Provides backward compatibility with existing workspaces based on
+ * descriptions.
+ */
+ public WorkspaceDescription readOldWorkspace() {
+ IPath path = getOldWorkspaceDescriptionLocation();
+ IPath tempPath = getBackupLocationFor(path);
+ try {
+ WorkspaceDescription oldDescription = (WorkspaceDescription) new WorkspaceDescriptionReader().read(path, tempPath);
+ // if one of those files exist, get rid of them
+ Workspace.clear(path.toFile());
+ Workspace.clear(tempPath.toFile());
+ return oldDescription;
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Returns the portions of the project description that are private, and
+ * adds them to the supplied project description. In particular, the
+ * project location, the project's dynamic references and build configurations
+ * are stored here.
+ * The project location will be set to null if the default
+ * location should be used. In the case of failure, log the exception and
+ * return silently, thus reverting to using the default location and no
+ * dynamic references. The current format of the location file is:
+ * UTF - project location
+ * int - number of dynamic project references
+ * UTF - project reference 1
+ * ... repeat for remaining references
+ * since 3.7:
+ * int - number of build configurations
+ * UTF - configuration name in order
+ * ... repeated for N configurations
+ * UTF - active build configuration name
+ * int - number of build configurations with refs
+ * UTF - build configuration name
+ * int - number of referenced configuration
+ * UTF - project name
+ * bool - hasConfigName
+ * UTF - configName if hasConfigName
+ * ... repeat for number of referenced configurations
+ * ... repeat for number of build configurations with references
+ */
+ public void readPrivateDescription(IProject target, ProjectDescription description) {
+ IPath locationFile = locationFor(target).append(F_PROJECT_LOCATION);
+ java.io.File file = locationFile.toFile();
+ if (!file.exists()) {
+ locationFile = getBackupLocationFor(locationFile);
+ file = locationFile.toFile();
+ if (!file.exists())
+ return;
+ }
+ try {
+ SafeChunkyInputStream input = new SafeChunkyInputStream(file, 500);
+ DataInputStream dataIn = new DataInputStream(input);
+ try {
+ try {
+ String location = dataIn.readUTF();
+ if (location.length() > 0) {
+ //location format < 3.2 was a local file system OS path
+ //location format >= 3.2 is: URI_PREFIX + uri.toString()
+ if (location.startsWith(URI_PREFIX))
+ description.setLocationURI(URI.create(location.substring(URI_PREFIX.length())));
+ else
+ description.setLocationURI(URIUtil.toURI(Path.fromOSString(location)));
+ }
+ } catch (Exception e) {
+ //don't allow failure to read the location to propagate
+ String msg = NLS.bind(Messages.resources_exReadProjectLocation, target.getName());
+ Policy.log(new ResourceStatus(IStatus.ERROR, IResourceStatus.FAILED_READ_METADATA, target.getFullPath(), msg, e));
+ }
+ //try to read the dynamic references - will fail for old location files
+ int numRefs = dataIn.readInt();
+ IProject[] references = new IProject[numRefs];
+ IWorkspaceRoot root = getWorkspace().getRoot();
+ for (int i = 0; i < numRefs; i++)
+ references[i] = root.getProject(dataIn.readUTF());
+ description.setDynamicReferences(references);
+
+ // Since 3.7 - Build Configurations
+ String[] configs = new String[dataIn.readInt()];
+ for (int i = 0; i < configs.length; i++)
+ configs[i] = dataIn.readUTF();
+ if (configs.length > 0)
+ // In the future we may decide this is better stored in the
+ // .project, so only set if configs.length > 0
+ description.setBuildConfigs(configs);
+ // Active configuration name
+ description.setActiveBuildConfig(dataIn.readUTF());
+ // Build configuration references?
+ int numBuildConifgsWithRefs = dataIn.readInt();
+ HashMap m = new HashMap(numBuildConifgsWithRefs);
+ for (int i = 0; i < numBuildConifgsWithRefs; i++) {
+ String configName = dataIn.readUTF();
+ numRefs = dataIn.readInt();
+ IBuildConfiguration[] refs = new IBuildConfiguration[numRefs];
+ for (int j = 0; j < numRefs; j++) {
+ String projName = dataIn.readUTF();
+ if (dataIn.readBoolean())
+ refs[j] = new BuildConfiguration(root.getProject(projName), dataIn.readUTF());
+ else
+ refs[j] = new BuildConfiguration(root.getProject(projName), null);
+ }
+ m.put(configName, refs);
+ }
+ description.setBuildConfigReferences(m);
+ } finally {
+ dataIn.close();
+ }
+ } catch (IOException e) {
+ //ignore - this is an old location file or an exception occurred
+ // closing the stream
+ }
+ }
+
+ /**
+ * Writes the workspace description to the local meta area. This method is
+ * synchronized to prevent multiple current write attempts.
+ *
+ * @deprecated should not be called any more - workspace preferences are
+ * now maintained in the plug-in's preferences
+ */
+ @Deprecated
+ public synchronized void write(WorkspaceDescription description) throws CoreException {
+ IPath path = getOldWorkspaceDescriptionLocation();
+ path.toFile().getParentFile().mkdirs();
+ IPath tempPath = getBackupLocationFor(path);
+ try {
+ new ModelObjectWriter().write(description, path, tempPath, System.getProperty("line.separator")); //$NON-NLS-1$
+ } catch (IOException e) {
+ String message = NLS.bind(Messages.resources_writeWorkspaceMeta, path);
+ throw new ResourceException(IResourceStatus.FAILED_WRITE_METADATA, null, message, e);
+ }
+ }
+
+ /**
+ * Write the private project description information, including the location
+ * and the dynamic project references. See readPrivateDescription
+ * for details on the file format.
+ */
+ public void writePrivateDescription(IProject target) throws CoreException {
+ IPath location = locationFor(target).append(F_PROJECT_LOCATION);
+ java.io.File file = location.toFile();
+ //delete any old location file
+ Workspace.clear(file);
+ //don't write anything if there is no interesting private metadata
+ ProjectDescription desc = ((Project) target).internalGetDescription();
+ if (desc == null)
+ return;
+ final URI projectLocation = desc.getLocationURI();
+ final IProject[] prjRefs = desc.getDynamicReferences(false);
+ final String[] buildConfigs = desc.configNames;
+ final Map configRefs = desc.getBuildConfigReferences(false);
+ if (projectLocation == null && prjRefs.length == 0 && buildConfigs.length == 0 && configRefs.isEmpty())
+ return;
+ //write the private metadata file
+ try {
+ SafeChunkyOutputStream output = new SafeChunkyOutputStream(file);
+ DataOutputStream dataOut = new DataOutputStream(output);
+ try {
+ if (projectLocation == null)
+ dataOut.writeUTF(""); //$NON-NLS-1$
+ else
+ dataOut.writeUTF(URI_PREFIX + projectLocation.toString());
+ dataOut.writeInt(prjRefs.length);
+ for (int i = 0; i < prjRefs.length; i++)
+ dataOut.writeUTF(prjRefs[i].getName());
+
+ // Since 3.7 - build configurations + references
+ // Write out the build configurations
+ dataOut.writeInt(buildConfigs.length);
+ for (int i = 0; i < buildConfigs.length; i++) {
+ dataOut.writeUTF(buildConfigs[i]);
+ }
+ // Write active configuration name
+ dataOut.writeUTF(desc.getActiveBuildConfig());
+ // Write out the configuration level references
+ dataOut.writeInt(configRefs.size());
+ for (Map.Entry e : configRefs.entrySet()) {
+ String refdName = e.getKey();
+ IBuildConfiguration[] refs = e.getValue();
+
+ dataOut.writeUTF(refdName);
+ dataOut.writeInt(refs.length);
+ for (int j = 0; j < refs.length; j++) {
+ dataOut.writeUTF(refs[j].getProject().getName());
+ if (refs[j].getName() == null) {
+ dataOut.writeBoolean(false);
+ } else {
+ dataOut.writeBoolean(true);
+ dataOut.writeUTF(refs[j].getName());
+ }
+ }
+ }
+ output.succeed();
+ dataOut.close();
+ } finally {
+ FileUtil.safeClose(dataOut);
+ }
+ } catch (IOException e) {
+ String message = NLS.bind(Messages.resources_exSaveProjectLocation, target.getName());
+ throw new ResourceException(IResourceStatus.INTERNAL_ERROR, null, message, e);
+ }
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/LocationValidator.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/LocationValidator.java
new file mode 100644
index 0000000000..8e0ea9a01b
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/LocationValidator.java
@@ -0,0 +1,438 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2014 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * Serge Beauchamp (Freescale Semiconductor) - [229633] Project Path Variable Support
+ *******************************************************************************/
+package org.eclipse.core.internal.resources;
+
+import java.net.URI;
+import org.eclipse.core.filesystem.EFS;
+import org.eclipse.core.filesystem.URIUtil;
+import org.eclipse.core.internal.utils.FileUtil;
+import org.eclipse.core.internal.utils.Messages;
+import org.eclipse.core.resources.*;
+import org.eclipse.core.runtime.*;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * This class implements the various path, URI, and name validation methods
+ * in the workspace API
+ */
+public class LocationValidator {
+ private final Workspace workspace;
+
+ public LocationValidator(Workspace workspace) {
+ this.workspace = workspace;
+ }
+
+ /**
+ * Returns a string representation of a URI suitable for displaying to an end user.
+ */
+ private String toString(URI uri) {
+ try {
+ return EFS.getStore(uri).toString();
+ } catch (CoreException e) {
+ //there is no store defined, so the best we can do is the URI toString.
+ return uri.toString();
+ }
+ }
+
+ /**
+ * Check that the location is absolute
+ */
+ private IStatus validateAbsolute(URI location, boolean error) {
+ if (!location.isAbsolute()) {
+ String message;
+ if (location.getSchemeSpecificPart() == null)
+ message = Messages.links_noPath;
+ else {
+ IPath pathPart = new Path(location.getSchemeSpecificPart());
+ if (pathPart.segmentCount() > 0)
+ message = NLS.bind(Messages.pathvar_undefined, location.toString(), pathPart.segment(0));
+ else
+ message = Messages.links_noPath;
+ }
+ int code = error ? IResourceStatus.VARIABLE_NOT_DEFINED : IResourceStatus.VARIABLE_NOT_DEFINED_WARNING;
+ return new ResourceStatus(code, null, message);
+ }
+ return Status.OK_STATUS;
+ }
+
+ /* (non-Javadoc)
+ * @see IWorkspace#validateLinkLocation(IResource, IPath)
+ */
+ public IStatus validateLinkLocation(IResource resource, IPath unresolvedLocation) {
+ IPath location = resource.getPathVariableManager().resolvePath(unresolvedLocation);
+ if (location.isEmpty())
+ return new ResourceStatus(IResourceStatus.INVALID_VALUE, resource.getFullPath(), Messages.links_noPath);
+ //check that the location is absolute
+ if (!location.isAbsolute()) {
+ //we know there is at least one segment, because of previous isEmpty check
+ String message = NLS.bind(Messages.pathvar_undefined, location.toOSString(), location.segment(0));
+ return new ResourceStatus(IResourceStatus.VARIABLE_NOT_DEFINED_WARNING, resource.getFullPath(), message);
+ }
+ //if the location doesn't have a device, see if the OS will assign one
+ if (location.getDevice() == null)
+ location = new Path(location.toFile().getAbsolutePath());
+ return validateLinkLocationURI(resource, URIUtil.toURI(location));
+ }
+
+ public IStatus validateLinkLocationURI(IResource resource, URI unresolvedLocation) {
+ if (unresolvedLocation.getSchemeSpecificPart() == null)
+ return new ResourceStatus(IResourceStatus.INVALID_VALUE, resource.getFullPath(), Messages.links_noPath);
+ String message;
+ //check if resource linking is disabled
+ if (ResourcesPlugin.getPlugin().getPluginPreferences().getBoolean(ResourcesPlugin.PREF_DISABLE_LINKING)) {
+ message = NLS.bind(Messages.links_workspaceVeto, resource.getName());
+ return new ResourceStatus(IResourceStatus.INVALID_VALUE, resource.getFullPath(), message);
+ }
+ //check that the resource is the right type
+ int type = resource.getType();
+ if (type != IResource.FOLDER && type != IResource.FILE) {
+ message = NLS.bind(Messages.links_notFileFolder, resource.getName());
+ return new ResourceStatus(IResourceStatus.INVALID_VALUE, resource.getFullPath(), message);
+ }
+ IContainer parent = resource.getParent();
+ if (!parent.isAccessible()) {
+ message = NLS.bind(Messages.links_parentNotAccessible, resource.getFullPath());
+ return new ResourceStatus(IResourceStatus.INVALID_VALUE, resource.getFullPath(), message);
+ }
+ URI location = resource.getPathVariableManager().resolveURI(unresolvedLocation);
+ //check nature veto
+ String[] natureIds = ((Project) resource.getProject()).internalGetDescription().getNatureIds();
+
+ IStatus result = workspace.getNatureManager().validateLinkCreation(natureIds);
+ if (!result.isOK())
+ return result;
+ //check team provider veto
+ if (resource.getType() == IResource.FILE)
+ result = workspace.getTeamHook().validateCreateLink((IFile) resource, IResource.NONE, location);
+ else
+ result = workspace.getTeamHook().validateCreateLink((IFolder) resource, IResource.NONE, location);
+ if (!result.isOK())
+ return result;
+ //check the standard path name restrictions
+ result = validateSegments(location);
+ if (!result.isOK())
+ return result;
+ //check if the location is based on an undefined variable
+ result = validateAbsolute(location, false);
+ if (!result.isOK())
+ return result;
+ // test if the given location overlaps the platform metadata location
+ URI testLocation = workspace.getMetaArea().getLocation().toFile().toURI();
+ if (FileUtil.isOverlapping(location, testLocation)) {
+ message = NLS.bind(Messages.links_invalidLocation, toString(location));
+ return new ResourceStatus(IResourceStatus.INVALID_VALUE, resource.getFullPath(), message);
+ }
+ //test if the given path overlaps the location of the given project
+ testLocation = resource.getProject().getLocationURI();
+ if (testLocation != null && FileUtil.isPrefixOf(location, testLocation)) {
+ message = NLS.bind(Messages.links_locationOverlapsProject, toString(location));
+ return new ResourceStatus(IResourceStatus.INVALID_VALUE, resource.getFullPath(), message);
+ }
+ //warnings (all errors must be checked before all warnings)
+
+ // Iterate over each known project and ensure that the location does not
+ // conflict with any project locations or linked resource locations
+ IProject[] projects = workspace.getRoot().getProjects(IContainer.INCLUDE_HIDDEN);
+ for (int i = 0; i < projects.length; i++) {
+ IProject project = projects[i];
+ // since we are iterating over the project in the workspace, we
+ // know that they have been created before and must have a description
+ IProjectDescription desc = ((Project) project).internalGetDescription();
+ testLocation = desc.getLocationURI();
+ if (testLocation != null && FileUtil.isOverlapping(location, testLocation)) {
+ message = NLS.bind(Messages.links_overlappingResource, toString(location));
+ return new ResourceStatus(IResourceStatus.OVERLAPPING_LOCATION, resource.getFullPath(), message);
+ }
+ //iterate over linked resources and check for overlap
+ if (!project.isOpen())
+ continue;
+ IResource[] children = null;
+ try {
+ children = project.members();
+ } catch (CoreException e) {
+ //ignore projects that cannot be accessed
+ }
+ if (children == null)
+ continue;
+ for (int j = 0; j < children.length; j++) {
+ if (children[j].isLinked()) {
+ testLocation = children[j].getLocationURI();
+ if (testLocation != null && FileUtil.isOverlapping(location, testLocation)) {
+ message = NLS.bind(Messages.links_overlappingResource, toString(location));
+ return new ResourceStatus(IResourceStatus.OVERLAPPING_LOCATION, resource.getFullPath(), message);
+ }
+ }
+ }
+ }
+ return Status.OK_STATUS;
+ }
+
+ /* (non-Javadoc)
+ * @see IWorkspace#validateName(String, int)
+ */
+ public IStatus validateName(String segment, int type) {
+ String message;
+
+ /* segment must not be null */
+ if (segment == null) {
+ message = Messages.resources_nameNull;
+ return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
+ }
+
+ // cannot be an empty string
+ if (segment.length() == 0) {
+ message = Messages.resources_nameEmpty;
+ return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
+ }
+
+ /* test invalid characters */
+ char[] chars = OS.INVALID_RESOURCE_CHARACTERS;
+ for (int i = 0; i < chars.length; i++)
+ if (segment.indexOf(chars[i]) != -1) {
+ message = NLS.bind(Messages.resources_invalidCharInName, String.valueOf(chars[i]), segment);
+ return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
+ }
+
+ /* test invalid OS names */
+ if (!OS.isNameValid(segment)) {
+ message = NLS.bind(Messages.resources_invalidName, segment);
+ return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
+ }
+ return Status.OK_STATUS;
+ }
+
+ /**
+ * Validates that the given workspace path is valid for the given type. If
+ * lastSegmentOnly is true, it is assumed that all segments except
+ * the last one have previously been validated. This is an optimization for validating
+ * a leaf resource when it is known that the parent exists (and thus its parent path
+ * must already be valid).
+ */
+ public IStatus validatePath(IPath path, int type, boolean lastSegmentOnly) {
+ String message;
+
+ /* path must not be null */
+ if (path == null) {
+ message = Messages.resources_pathNull;
+ return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
+ }
+
+ /* path must not have a device separator */
+ if (path.getDevice() != null) {
+ message = NLS.bind(Messages.resources_invalidCharInPath, String.valueOf(IPath.DEVICE_SEPARATOR), path);
+ return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
+ }
+
+ /* path must not be the root path */
+ if (path.isRoot()) {
+ message = Messages.resources_invalidRoot;
+ return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
+ }
+
+ /* path must be absolute */
+ if (!path.isAbsolute()) {
+ message = NLS.bind(Messages.resources_mustBeAbsolute, path);
+ return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
+ }
+
+ /* validate segments */
+ int numberOfSegments = path.segmentCount();
+ if ((type & IResource.PROJECT) != 0) {
+ if (numberOfSegments == ICoreConstants.PROJECT_SEGMENT_LENGTH) {
+ return validateName(path.segment(0), IResource.PROJECT);
+ } else if (type == IResource.PROJECT) {
+ message = NLS.bind(Messages.resources_projectPath, path);
+ return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
+ }
+ }
+ if ((type & (IResource.FILE | IResource.FOLDER)) != 0) {
+ if (numberOfSegments < ICoreConstants.MINIMUM_FILE_SEGMENT_LENGTH) {
+ message = NLS.bind(Messages.resources_resourcePath, path);
+ return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
+ }
+ int fileFolderType = type &= ~IResource.PROJECT;
+ int segmentCount = path.segmentCount();
+ if (lastSegmentOnly)
+ return validateName(path.segment(segmentCount - 1), fileFolderType);
+ IStatus status = validateName(path.segment(0), IResource.PROJECT);
+ if (!status.isOK())
+ return status;
+ // ignore first segment (the project)
+ for (int i = 1; i < segmentCount; i++) {
+ status = validateName(path.segment(i), fileFolderType);
+ if (!status.isOK())
+ return status;
+ }
+ return Status.OK_STATUS;
+ }
+ message = NLS.bind(Messages.resources_invalidPath, path);
+ return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
+ }
+
+ /* (non-Javadoc)
+ * @see IWorkspace#validatePath(String, int)
+ */
+ public IStatus validatePath(String path, int type) {
+ /* path must not be null */
+ if (path == null) {
+ String message = Messages.resources_pathNull;
+ return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
+ }
+ return validatePath(Path.fromOSString(path), type, false);
+ }
+
+ public IStatus validateProjectLocation(IProject context, IPath unresolvedLocation) {
+ if (unresolvedLocation == null)
+ return validateProjectLocationURI(context, null);
+ IPath location;
+ if (context != null)
+ location = context.getPathVariableManager().resolvePath(unresolvedLocation);
+ else
+ location = workspace.getPathVariableManager().resolvePath(unresolvedLocation);
+ //check that the location is absolute
+ if (!location.isAbsolute()) {
+ String message;
+ if (location.segmentCount() > 0)
+ message = NLS.bind(Messages.pathvar_undefined, location.toString(), location.segment(0));
+ else
+ message = Messages.links_noPath;
+ return new ResourceStatus(IResourceStatus.VARIABLE_NOT_DEFINED, null, message);
+ }
+ return validateProjectLocationURI(context, URIUtil.toURI(location));
+ }
+
+ /* (non-Javadoc)
+ * @see IWorkspace#validateProjectLocationURI(IProject, URI)
+ */
+ public IStatus validateProjectLocationURI(IProject context, URI unresolvedLocation) {
+ if (context == null && unresolvedLocation == null)
+ throw new IllegalArgumentException("Either a project or a location must be provided"); //$NON-NLS-1$
+
+ // Checks if the new location overlaps the workspace metadata location
+ boolean isMetadataLocation = false;
+
+ if (unresolvedLocation != null) {
+ if (URIUtil.equals(unresolvedLocation, URIUtil.toURI(Platform.getLocation().addTrailingSeparator().append(LocalMetaArea.F_METADATA)))) {
+ isMetadataLocation = true;
+ }
+ } else if (context != null && context.getName().equals(LocalMetaArea.F_METADATA)) {
+ isMetadataLocation = true;
+ }
+
+ String message;
+ if (isMetadataLocation) {
+ message = NLS.bind(Messages.resources_invalidPath, toString(URIUtil.toURI(Platform.getLocation().addTrailingSeparator().append(LocalMetaArea.F_METADATA))));
+ return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
+ }
+
+ // the default is ok for all other projects
+ if (unresolvedLocation == null)
+ return Status.OK_STATUS;
+ URI location;
+ if (context != null)
+ location = context.getPathVariableManager().resolveURI(unresolvedLocation);
+ else
+ location = workspace.getPathVariableManager().resolveURI(unresolvedLocation);
+ //check the standard path name restrictions
+ IStatus result = validateSegments(location);
+ if (!result.isOK())
+ return result;
+ result = validateAbsolute(location, true);
+ if (!result.isOK())
+ return result;
+ //check that the URI has a legal scheme
+ try {
+ EFS.getFileSystem(location.getScheme());
+ } catch (CoreException e) {
+ return e.getStatus();
+ }
+ //overlaps with default location can only occur with file URIs
+ if (location.getScheme().equals(EFS.SCHEME_FILE)) {
+ IPath locationPath = URIUtil.toPath(location);
+ // test if the given location overlaps the default default location
+ IPath defaultDefaultLocation = workspace.getRoot().getLocation();
+ if (FileUtil.isPrefixOf(locationPath, defaultDefaultLocation)) {
+ message = NLS.bind(Messages.resources_overlapWorkspace, toString(location), defaultDefaultLocation.toOSString());
+ return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
+ }
+ // Test if the given location is the default location for any potential project except
+ // the one being created.
+ IPath parentPath = locationPath.removeLastSegments(1);
+ if (FileUtil.isPrefixOf(parentPath, defaultDefaultLocation) && FileUtil.isPrefixOf(defaultDefaultLocation, parentPath) && (context == null || !locationPath.equals(defaultDefaultLocation.append(context.getName())))) {
+ message = NLS.bind(Messages.resources_overlapProject, toString(location), locationPath.lastSegment());
+ return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
+ }
+ }
+
+ // Iterate over each known project and ensure that the location does not
+ // conflict with any of their already defined locations.
+ IProject[] projects = workspace.getRoot().getProjects(IContainer.INCLUDE_HIDDEN);
+ for (int j = 0; j < projects.length; j++) {
+ IProject project = projects[j];
+ URI testLocation = project.getLocationURI();
+ if (context != null && project.equals(context)) {
+ //tolerate locations being the same if this is the project being tested
+ if (URIUtil.equals(testLocation, location))
+ continue;
+ //a project cannot be moved inside of its current location
+ if (!FileUtil.isPrefixOf(testLocation, location))
+ continue;
+ } else if (!URIUtil.equals(testLocation, location)) {
+ // a project cannot have the same location as another existing project
+ continue;
+ }
+ //in all other cases there is illegal overlap
+ message = NLS.bind(Messages.resources_overlapProject, toString(location), project.getName());
+ return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
+ }
+ //if this project exists and has linked resources, the project location cannot overlap
+ //the locations of any linked resources in that project
+ if (context != null && context.exists() && context.isOpen()) {
+ IResource[] children = null;
+ try {
+ children = context.members();
+ } catch (CoreException e) {
+ //ignore projects that cannot be accessed
+ }
+ if (children != null) {
+ for (int i = 0; i < children.length; i++) {
+ if (children[i].isLinked()) {
+ URI testLocation = children[i].getLocationURI();
+ if (testLocation != null && FileUtil.isPrefixOf(testLocation, location)) {
+ message = NLS.bind(Messages.links_locationOverlapsLink, toString(location));
+ return new ResourceStatus(IResourceStatus.OVERLAPPING_LOCATION, context.getFullPath(), message);
+ }
+ }
+ }
+ }
+ }
+ return Status.OK_STATUS;
+ }
+
+ /**
+ * Validates the standard path name restrictions on the segments of the provided URI.
+ * @param location The URI to validate
+ * @return A status indicating if the segments of the provided URI are valid
+ */
+ private IStatus validateSegments(URI location) {
+ if (EFS.SCHEME_FILE.equals(location.getScheme())) {
+ IPath pathPart = new Path(location.getSchemeSpecificPart());
+ int segmentCount = pathPart.segmentCount();
+ for (int i = 0; i < segmentCount; i++) {
+ IStatus result = validateName(pathPart.segment(i), IResource.PROJECT);
+ if (!result.isOK())
+ return result;
+ }
+ }
+ return Status.OK_STATUS;
+ }
+}
\ No newline at end of file
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Marker.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Marker.java
new file mode 100644
index 0000000000..b6acf36bcb
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Marker.java
@@ -0,0 +1,339 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2015 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * James Blackburn (Broadcom Corp.) - ongoing development
+ *******************************************************************************/
+package org.eclipse.core.internal.resources;
+
+import java.util.Map;
+import org.eclipse.core.internal.utils.Messages;
+import org.eclipse.core.resources.*;
+import org.eclipse.core.runtime.*;
+import org.eclipse.core.runtime.jobs.ISchedulingRule;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * An abstract marker implementation.
+ * Subclasses must implement the clone method, and
+ * are free to declare additional field and method members.
+ *
+ * Note: Marker objects do not store whether they are "standalone"
+ * vs. "attached" to the workspace. This information is maintained
+ * by the workspace.
+ *
+ *
+ * @see IMarker
+ */
+public class Marker extends PlatformObject implements IMarker {
+
+ /** Marker identifier. */
+ protected long id;
+
+ /** Resource with which this marker is associated. */
+ protected IResource resource;
+
+ /**
+ * Constructs a new marker object.
+ */
+ Marker(IResource resource, long id) {
+ Assert.isLegal(resource != null);
+ this.resource = resource;
+ this.id = id;
+ }
+
+ /**
+ * Checks the given marker info to ensure that it is not null.
+ * Throws an exception if it is.
+ */
+ private void checkInfo(MarkerInfo info) throws CoreException {
+ if (info == null) {
+ String message = NLS.bind(Messages.resources_markerNotFound, Long.toString(id));
+ throw new ResourceException(new ResourceStatus(IResourceStatus.MARKER_NOT_FOUND, resource.getFullPath(), message));
+ }
+ }
+
+ /**
+ * @see IMarker#delete()
+ */
+ @Override
+ public void delete() throws CoreException {
+ final ISchedulingRule rule = getWorkspace().getRuleFactory().markerRule(resource);
+ try {
+ getWorkspace().prepareOperation(rule, null);
+ getWorkspace().beginOperation(true);
+ getWorkspace().getMarkerManager().removeMarker(getResource(), getId());
+ } finally {
+ getWorkspace().endOperation(rule, false, null);
+ }
+ }
+
+ /**
+ * @see IMarker#equals(Object)
+ */
+ @Override
+ public boolean equals(Object object) {
+ if (!(object instanceof IMarker))
+ return false;
+ IMarker other = (IMarker) object;
+ return (id == other.getId() && resource.equals(other.getResource()));
+ }
+
+ /**
+ * @see IMarker#exists()
+ */
+ @Override
+ public boolean exists() {
+ return getInfo() != null;
+ }
+
+ /**
+ * @see IMarker#getAttribute(String)
+ */
+ @Override
+ public Object getAttribute(String attributeName) throws CoreException {
+ Assert.isNotNull(attributeName);
+ MarkerInfo info = getInfo();
+ checkInfo(info);
+ return info.getAttribute(attributeName);
+ }
+
+ /**
+ * @see IMarker#getAttribute(String, int)
+ */
+ @Override
+ public int getAttribute(String attributeName, int defaultValue) {
+ Assert.isNotNull(attributeName);
+ MarkerInfo info = getInfo();
+ if (info == null)
+ return defaultValue;
+ Object value = info.getAttribute(attributeName);
+ if (value instanceof Integer)
+ return ((Integer) value).intValue();
+ return defaultValue;
+ }
+
+ /**
+ * @see IMarker#getAttribute(String, String)
+ */
+ @Override
+ public String getAttribute(String attributeName, String defaultValue) {
+ Assert.isNotNull(attributeName);
+ MarkerInfo info = getInfo();
+ if (info == null)
+ return defaultValue;
+ Object value = info.getAttribute(attributeName);
+ if (value instanceof String)
+ return (String) value;
+ return defaultValue;
+ }
+
+ /**
+ * @see IMarker#getAttribute(String, boolean)
+ */
+ @Override
+ public boolean getAttribute(String attributeName, boolean defaultValue) {
+ Assert.isNotNull(attributeName);
+ MarkerInfo info = getInfo();
+ if (info == null)
+ return defaultValue;
+ Object value = info.getAttribute(attributeName);
+ if (value instanceof Boolean)
+ return ((Boolean) value).booleanValue();
+ return defaultValue;
+ }
+
+ /**
+ * @see IMarker#getAttributes()
+ */
+ @Override
+ public Map getAttributes() throws CoreException {
+ MarkerInfo info = getInfo();
+ checkInfo(info);
+ return info.getAttributes();
+ }
+
+ /**
+ * @see IMarker#getAttributes(String[])
+ */
+ @Override
+ public Object[] getAttributes(String[] attributeNames) throws CoreException {
+ Assert.isNotNull(attributeNames);
+ MarkerInfo info = getInfo();
+ checkInfo(info);
+ return info.getAttributes(attributeNames);
+ }
+
+ /**
+ * @see IMarker#getCreationTime()
+ */
+ @Override
+ public long getCreationTime() throws CoreException {
+ MarkerInfo info = getInfo();
+ checkInfo(info);
+ return info.getCreationTime();
+ }
+
+ /**
+ * @see IMarker#getId()
+ */
+ @Override
+ public long getId() {
+ return id;
+ }
+
+ protected MarkerInfo getInfo() {
+ return getWorkspace().getMarkerManager().findMarkerInfo(resource, id);
+ }
+
+ /**
+ * @see IMarker#getResource()
+ */
+ @Override
+ public IResource getResource() {
+ return resource;
+ }
+
+ /**
+ * @see IMarker#getType()
+ */
+ @Override
+ public String getType() throws CoreException {
+ MarkerInfo info = getInfo();
+ checkInfo(info);
+ return info.getType();
+ }
+
+ /**
+ * Returns the workspace which manages this marker. Returns
+ * null if this resource does not have an associated
+ * resource.
+ */
+ private Workspace getWorkspace() {
+ return resource == null ? null : (Workspace) resource.getWorkspace();
+ }
+
+ @Override
+ public int hashCode() {
+ return (int) id + resource.hashCode();
+ }
+
+ /**
+ * @see IMarker#isSubtypeOf(String)
+ */
+ @Override
+ public boolean isSubtypeOf(String type) throws CoreException {
+ return getWorkspace().getMarkerManager().isSubtype(getType(), type);
+ }
+
+ /**
+ * @see IMarker#setAttribute(String, int)
+ */
+ @Override
+ public void setAttribute(String attributeName, int value) throws CoreException {
+ setAttribute(attributeName, new Integer(value));
+ }
+
+ /**
+ * @see IMarker#setAttribute(String, Object)
+ */
+ @Override
+ public void setAttribute(String attributeName, Object value) throws CoreException {
+ Assert.isNotNull(attributeName);
+ Workspace workspace = getWorkspace();
+ MarkerManager manager = workspace.getMarkerManager();
+ try {
+ workspace.prepareOperation(null, null);
+ workspace.beginOperation(true);
+ MarkerInfo markerInfo = getInfo();
+ checkInfo(markerInfo);
+
+ //only need to generate delta info if none already
+ boolean needDelta = !manager.hasDelta(resource.getFullPath(), id);
+ MarkerInfo oldInfo = needDelta ? (MarkerInfo) markerInfo.clone() : null;
+ boolean validate = manager.isPersistentType(markerInfo.getType());
+ markerInfo.setAttribute(attributeName, value, validate);
+ if (manager.isPersistent(markerInfo))
+ ((Resource) resource).getResourceInfo(false, true).set(ICoreConstants.M_MARKERS_SNAP_DIRTY);
+ if (needDelta) {
+ MarkerDelta delta = new MarkerDelta(IResourceDelta.CHANGED, resource, oldInfo);
+ manager.changedMarkers(resource, new MarkerDelta[] {delta});
+ }
+ } finally {
+ workspace.endOperation(null, false, null);
+ }
+ }
+
+ /**
+ * @see IMarker#setAttribute(String, boolean)
+ */
+ @Override
+ public void setAttribute(String attributeName, boolean value) throws CoreException {
+ setAttribute(attributeName, value ? Boolean.TRUE : Boolean.FALSE);
+ }
+
+ /**
+ * @see IMarker#setAttributes(String[], Object[])
+ */
+ @Override
+ public void setAttributes(String[] attributeNames, Object[] values) throws CoreException {
+ Assert.isNotNull(attributeNames);
+ Assert.isNotNull(values);
+ Workspace workspace = getWorkspace();
+ MarkerManager manager = workspace.getMarkerManager();
+ try {
+ workspace.prepareOperation(null, null);
+ workspace.beginOperation(true);
+ MarkerInfo markerInfo = getInfo();
+ checkInfo(markerInfo);
+
+ //only need to generate delta info if none already
+ boolean needDelta = !manager.hasDelta(resource.getFullPath(), id);
+ MarkerInfo oldInfo = needDelta ? (MarkerInfo) markerInfo.clone() : null;
+ boolean validate = manager.isPersistentType(markerInfo.getType());
+ markerInfo.setAttributes(attributeNames, values, validate);
+ if (manager.isPersistent(markerInfo))
+ ((Resource) resource).getResourceInfo(false, true).set(ICoreConstants.M_MARKERS_SNAP_DIRTY);
+ if (needDelta) {
+ MarkerDelta delta = new MarkerDelta(IResourceDelta.CHANGED, resource, oldInfo);
+ manager.changedMarkers(resource, new MarkerDelta[] {delta});
+ }
+ } finally {
+ workspace.endOperation(null, false, null);
+ }
+ }
+
+ /**
+ * @see IMarker#setAttributes(Map)
+ */
+ @Override
+ public void setAttributes(Map values) throws CoreException {
+ Workspace workspace = getWorkspace();
+ MarkerManager manager = workspace.getMarkerManager();
+ try {
+ workspace.prepareOperation(null, null);
+ workspace.beginOperation(true);
+ MarkerInfo markerInfo = getInfo();
+ checkInfo(markerInfo);
+
+ //only need to generate delta info if none already
+ boolean needDelta = !manager.hasDelta(resource.getFullPath(), id);
+ MarkerInfo oldInfo = needDelta ? (MarkerInfo) markerInfo.clone() : null;
+ boolean validate = manager.isPersistentType(markerInfo.getType());
+ markerInfo.setAttributes(values, validate);
+ if (manager.isPersistent(markerInfo))
+ ((Resource) resource).getResourceInfo(false, true).set(ICoreConstants.M_MARKERS_SNAP_DIRTY);
+ if (needDelta) {
+ MarkerDelta delta = new MarkerDelta(IResourceDelta.CHANGED, resource, oldInfo);
+ manager.changedMarkers(resource, new MarkerDelta[] {delta});
+ }
+ } finally {
+ workspace.endOperation(null, false, null);
+ }
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerAttributeMap.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerAttributeMap.java
new file mode 100644
index 0000000000..53e83d260e
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerAttributeMap.java
@@ -0,0 +1,333 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2015 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * James Blackburn (Broadcom Corp.) - ongoing development
+ *******************************************************************************/
+package org.eclipse.core.internal.resources;
+
+import java.util.*;
+import org.eclipse.core.internal.utils.IStringPoolParticipant;
+import org.eclipse.core.internal.utils.StringPool;
+
+/**
+ * A specialized map implementation that is optimized for a
+ * small set of interned strings as keys. The provided keys
+ * MUST be instances of java.lang.String.
+ *
+ * Implemented as a single array that alternates keys and values.
+ */
+@SuppressWarnings("unchecked")
+public class MarkerAttributeMap implements Map, IStringPoolParticipant {
+ protected Object[] elements = null;
+ protected int count = 0;
+
+ // 8 attribute keys, 8 attribute values
+ protected static final int DEFAULT_SIZE = 16;
+ protected static final int GROW_SIZE = 10;
+
+ private static final Object[] EMPTY = new Object[0];
+
+ /**
+ * Creates a new marker attribute map of default size
+ */
+ public MarkerAttributeMap() {
+ this(DEFAULT_SIZE);
+ }
+
+ /**
+ * Creates a new marker attribute map.
+ * @param initialCapacity The initial number of elements that will fit in the map.
+ */
+ public MarkerAttributeMap(int initialCapacity) {
+ elements = initialCapacity > 0 ? new Object[initialCapacity * 2] : EMPTY;
+ }
+
+ /**
+ * Creates a new marker attribute map of default size
+ * @param map The entries in the given map will be added to the new map.
+ */
+ public MarkerAttributeMap(Map map) {
+ this(map.size());
+ putAll(map);
+ }
+
+ /* (non-Javadoc)
+ * @see Map#clear()
+ */
+ @Override
+ public void clear() {
+ count = 0;
+ elements = EMPTY;
+ }
+
+ /* (non-Javadoc)
+ * @see Map#containsKey(java.lang.Object)
+ */
+ @Override
+ public boolean containsKey(Object key) {
+ if (count == 0)
+ return false;
+ key = ((String) key).intern();
+ for (int i = 0; i < elements.length; i = i + 2)
+ if (elements[i] == key)
+ return true;
+ return false;
+ }
+
+ /* (non-Javadoc)
+ * @see Map#containsValue(java.lang.Object)
+ */
+ @Override
+ public boolean containsValue(Object value) {
+ if (count == 0)
+ return false;
+ for (int i = 1; i < elements.length; i = i + 2)
+ if (elements[i] != null && elements[i].equals(value))
+ return true;
+ return false;
+ }
+
+ /* (non-Javadoc)
+ * @see Map#entrySet()
+ * This implementation does not conform properly to the specification
+ * in the Map interface. The returned collection will not be bound to
+ * this map and will not remain in sync with this map.
+ */
+ @Override
+ public Set> entrySet() {
+ return toHashMap().entrySet();
+ }
+
+ /* (non-Javadoc)
+ * @see Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof Map))
+ return false;
+ Map other = (Map) o;
+ //must be same size
+ if (count != other.size())
+ return false;
+
+ if (count == 0)
+ return true;
+
+ //keysets must be equal
+ if (!keySet().equals(other.keySet()))
+ return false;
+
+ //values for each key must be equal
+ for (int i = 0; i < elements.length; i = i + 2) {
+ if (elements[i] != null && (!elements[i + 1].equals(other.get(elements[i]))))
+ return false;
+ }
+ return true;
+ }
+
+ /* (non-Javadoc)
+ * @see Map#get(java.lang.Object)
+ */
+ @Override
+ public V get(Object key) {
+ if (count == 0)
+ return null;
+ key = ((String) key).intern();
+ for (int i = 0; i < elements.length; i = i + 2)
+ if (elements[i] == key)
+ return (V) elements[i + 1];
+ return null;
+ }
+
+ /**
+ * The capacity of the map has been exceeded, grow the array by
+ * GROW_SIZE to accomodate more entries.
+ */
+ protected void grow() {
+ Object[] expanded = new Object[elements.length + GROW_SIZE];
+ System.arraycopy(elements, 0, expanded, 0, elements.length);
+ elements = expanded;
+ }
+
+ /* (non-Javadoc)
+ * @see Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ int hash = 0;
+ if (count == 0)
+ return hash;
+ for (int i = 0; i < elements.length; i = i + 2) {
+ if (elements[i] != null) {
+ hash += elements[i].hashCode();
+ }
+ }
+ return hash;
+ }
+
+ /* (non-Javadoc)
+ * @see Map#isEmpty()
+ */
+ @Override
+ public boolean isEmpty() {
+ return count == 0;
+ }
+
+ /* (non-Javadoc)
+ * @see Map#keySet()
+ * This implementation does not conform properly to the specification
+ * in the Map interface. The returned collection will not be bound to
+ * this map and will not remain in sync with this map.
+ */
+ @Override
+ public Set keySet() {
+ Set result = new HashSet(size());
+ if (count == 0)
+ return result;
+ for (int i = 0; i < elements.length; i = i + 2) {
+ if (elements[i] != null) {
+ result.add((String) elements[i]);
+ }
+ }
+ return result;
+ }
+
+ /* (non-Javadoc)
+ * @see Map#put(java.lang.Object, java.lang.Object)
+ */
+ @Override
+ public V put(String k, V value) {
+ if (k == null)
+ throw new NullPointerException();
+ if (value == null)
+ return remove(k);
+ String key = k.intern();
+
+ if (elements.length <= (count * 2))
+ grow();
+
+ // handle the case where we don't have any attributes yet
+ if (count == 0) {
+ elements[0] = key;
+ elements[1] = value;
+ count++;
+ return null;
+ }
+
+ // replace existing value if it exists
+ for (int i = 0; i < elements.length; i = i + 2) {
+ if (elements[i] == key) {
+ Object oldValue = elements[i + 1];
+ elements[i + 1] = value;
+ return (V) oldValue;
+ }
+ }
+
+ // otherwise add it to the list of elements.
+ for (int i = 0; i < elements.length; i = i + 2) {
+ if (elements[i] == null) {
+ elements[i] = key;
+ elements[i + 1] = value;
+ count++;
+ return null;
+ }
+ }
+ return null;
+ }
+
+ /* (non-Javadoc)
+ * @see Map#putAll(java.util.Map)
+ */
+ @Override
+ public void putAll(Map extends String, ? extends V> map) {
+ for (Map.Entry extends String, ? extends V> e : map.entrySet())
+ put(e.getKey(), e.getValue());
+ }
+
+ /* (non-Javadoc)
+ * @see Map#remove(java.lang.Object)
+ */
+ @Override
+ public V remove(Object key) {
+ if (count == 0)
+ return null;
+ key = ((String) key).intern();
+ for (int i = 0; i < elements.length; i = i + 2) {
+ if (elements[i] == key) {
+ elements[i] = null;
+ Object result = elements[i + 1];
+ elements[i + 1] = null;
+ count--;
+ return (V) result;
+ }
+ }
+ return null;
+ }
+
+ /* (non-Javadoc)
+ * @see Map#size()
+ */
+ @Override
+ public int size() {
+ return count;
+ }
+
+ /* (non-Javadoc
+ * Method declared on IStringPoolParticipant
+ */
+ @Override
+ public void shareStrings(StringPool set) {
+ //copy elements for thread safety
+ Object[] array = elements;
+ if (array == null)
+ return;
+ //don't share keys because they are already interned
+ for (int i = 1; i < array.length; i = i + 2) {
+ Object o = array[i];
+ if (o instanceof String)
+ array[i] = set.add((String) o);
+ else if (o instanceof IStringPoolParticipant)
+ ((IStringPoolParticipant) o).shareStrings(set);
+ }
+ }
+
+ /**
+ * Creates a new hash map with the same contents as this map.
+ */
+ private HashMap toHashMap() {
+ HashMap result = new HashMap(size());
+ if (count == 0)
+ return result;
+ for (int i = 0; i < elements.length; i = i + 2) {
+ if (elements[i] != null) {
+ result.put((String) elements[i], (V) elements[i + 1]);
+ }
+ }
+ return result;
+ }
+
+ /* (non-Javadoc)
+ * @see Map#values()
+ * This implementation does not conform properly to the specification
+ * in the Map interface. The returned collection will not be bound to
+ * this map and will not remain in sync with this map.
+ */
+ @Override
+ public Collection values() {
+ Set result = new HashSet(size());
+ if (count == 0)
+ return result;
+ for (int i = 1; i < elements.length; i = i + 2) {
+ if (elements[i] != null) {
+ result.add((V) elements[i]);
+ }
+ }
+ return result;
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerDelta.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerDelta.java
new file mode 100644
index 0000000000..49e89734f3
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerDelta.java
@@ -0,0 +1,246 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2015 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * James Blackburn (Broadcom Corp.) - ongoing development
+ *******************************************************************************/
+package org.eclipse.core.internal.resources;
+
+import java.util.Iterator;
+import java.util.Map;
+import org.eclipse.core.resources.*;
+import org.eclipse.core.runtime.IPath;
+
+/**
+ * @see IMarkerDelta
+ */
+public class MarkerDelta implements IMarkerDelta, IMarkerSetElement {
+ protected int kind;
+ protected IResource resource;
+ protected MarkerInfo info;
+
+ /**
+ * Creates a new marker delta.
+ */
+ public MarkerDelta(int kind, IResource resource, MarkerInfo info) {
+ this.kind = kind;
+ this.resource = resource;
+ this.info = info;
+ }
+
+ /* (non-Javadoc)
+ * @see IMarkerDelta#getAttribute(String)
+ */
+ @Override
+ public Object getAttribute(String attributeName) {
+ return info.getAttribute(attributeName);
+ }
+
+ /* (non-Javadoc)
+ * @see IMarkerDelta#getAttribute(String, int)
+ */
+ @Override
+ public int getAttribute(String attributeName, int defaultValue) {
+ Object value = info.getAttribute(attributeName);
+ if (value instanceof Integer)
+ return ((Integer) value).intValue();
+ return defaultValue;
+ }
+
+ /* (non-Javadoc)
+ * @see IMarkerDelta#getAttribute(String, String)
+ */
+ @Override
+ public String getAttribute(String attributeName, String defaultValue) {
+ Object value = info.getAttribute(attributeName);
+ if (value instanceof String)
+ return (String) value;
+ return defaultValue;
+ }
+
+ /* (non-Javadoc)
+ * @see IMarkerDelta#getAttribute(String, boolean)
+ */
+ @Override
+ public boolean getAttribute(String attributeName, boolean defaultValue) {
+ Object value = info.getAttribute(attributeName);
+ if (value instanceof Boolean)
+ return ((Boolean) value).booleanValue();
+ return defaultValue;
+ }
+
+ /* (non-Javadoc)
+ * @see IMarkerDelta#getAttributes()
+ */
+ @Override
+ public Map getAttributes() {
+ return info.getAttributes();
+ }
+
+ /* (non-Javadoc)
+ * @see IMarkerDelta#getAttributes(String[])
+ */
+ @Override
+ public Object[] getAttributes(String[] attributeNames) {
+ return info.getAttributes(attributeNames);
+ }
+
+ /* (non-Javadoc)
+ * @see IMarkerDelta#getId()
+ */
+ @Override
+ public long getId() {
+ return info.getId();
+ }
+
+ /* (non-Javadoc)
+ * @see IMarkerDelta#getKind()
+ */
+ @Override
+ public int getKind() {
+ return kind;
+ }
+
+ /* (non-Javadoc)
+ * @see IMarkerDelta#getMarker()
+ */
+ @Override
+ public IMarker getMarker() {
+ return new Marker(resource, getId());
+ }
+
+ /* (non-Javadoc)
+ * @see IMarkerDelta#getResource()
+ */
+ @Override
+ public IResource getResource() {
+ return resource;
+ }
+
+ /* (non-Javadoc)
+ * @see IMarkerDelta#getType()
+ */
+ @Override
+ public String getType() {
+ return info.getType();
+ }
+
+ /* (non-Javadoc)
+ * @see IMarkerDelta#isSubtypeOf(String)
+ */
+ @Override
+ public boolean isSubtypeOf(String superType) {
+ return ((Workspace) getResource().getWorkspace()).getMarkerManager().isSubtype(getType(), superType);
+ }
+
+ /**
+ * Merge two Maps of (IPath->MarkerSet) representing changes. Use the old
+ * map to store the result so we don't have to build a new map to return.
+ */
+ public static Map merge(Map oldChanges, Map newChanges) {
+ if (oldChanges == null)
+ //don't worry about copying since the new changes are no longer used
+ return newChanges;
+ if (newChanges == null)
+ return oldChanges;
+ for (Iterator it = newChanges.keySet().iterator(); it.hasNext();) {
+ IPath key = it.next();
+ MarkerSet oldSet = oldChanges.get(key);
+ MarkerSet newSet = newChanges.get(key);
+ if (oldSet == null)
+ oldChanges.put(key, newSet);
+ else
+ merge(oldSet, newSet.elements());
+ }
+ return oldChanges;
+ }
+
+ /**
+ * Merge two sets of marker changes. Both sets must be on the same resource. Use the original set
+ * of changes to store the result so we don't have to build a completely different set to return.
+ *
+ * add + add = N/A
+ * add + remove = nothing (no delta)
+ * add + change = add
+ * remove + add = N/A
+ * remove + remove = N/A
+ * remove + change = N/A
+ * change + add = N/A
+ * change + change = change (note: info held onto by the marker delta should be that of the oldest change, and not replaced when composed)
+ * change + remove = remove (note: info held onto by the marker delta should be that of the oldest change, and not replaced when changed to a remove)
+ */
+ protected static MarkerSet merge(MarkerSet oldChanges, IMarkerSetElement[] newChanges) {
+ if (oldChanges == null) {
+ MarkerSet result = new MarkerSet(newChanges.length);
+ for (int i = 0; i < newChanges.length; i++)
+ result.add(newChanges[i]);
+ return result;
+ }
+ if (newChanges == null)
+ return oldChanges;
+
+ for (int i = 0; i < newChanges.length; i++) {
+ MarkerDelta newDelta = (MarkerDelta) newChanges[i];
+ MarkerDelta oldDelta = (MarkerDelta) oldChanges.get(newDelta.getId());
+ if (oldDelta == null) {
+ oldChanges.add(newDelta);
+ continue;
+ }
+ switch (oldDelta.getKind()) {
+ case IResourceDelta.ADDED :
+ switch (newDelta.getKind()) {
+ case IResourceDelta.ADDED :
+ // add + add = N/A
+ break;
+ case IResourceDelta.REMOVED :
+ // add + remove = nothing
+ // Remove the original ADD delta.
+ oldChanges.remove(oldDelta);
+ break;
+ case IResourceDelta.CHANGED :
+ // add + change = add
+ break;
+ }
+ break;
+ case IResourceDelta.REMOVED :
+ switch (newDelta.getKind()) {
+ case IResourceDelta.ADDED :
+ // remove + add = N/A
+ break;
+ case IResourceDelta.REMOVED :
+ // remove + remove = N/A
+ break;
+ case IResourceDelta.CHANGED :
+ // remove + change = N/A
+ break;
+ }
+ break;
+ case IResourceDelta.CHANGED :
+ switch (newDelta.getKind()) {
+ case IResourceDelta.ADDED :
+ // change + add = N/A
+ break;
+ case IResourceDelta.REMOVED :
+ // change + remove = remove
+ // Change the delta kind.
+ oldDelta.setKind(IResourceDelta.REMOVED);
+ break;
+ case IResourceDelta.CHANGED :
+ // change + change = change
+ break;
+ }
+ break;
+ }
+ }
+ return oldChanges;
+ }
+
+ private void setKind(int kind) {
+ this.kind = kind;
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerDeltaManager.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerDeltaManager.java
new file mode 100644
index 0000000000..8c9a640aa4
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerDeltaManager.java
@@ -0,0 +1,91 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM - Initial API and implementation
+ * James Blackburn (Broadcom Corp.) - ongoing development
+ *******************************************************************************/
+package org.eclipse.core.internal.resources;
+
+import java.util.*;
+import org.eclipse.core.runtime.IPath;
+
+/**
+ * The notification mechanism can request marker deltas for several overlapping intervals
+ * of time. This class maintains a history of marker deltas, and upon request can
+ * generate a map of marker deltas for any interval. This is done by maintaining
+ * batches of marker deltas keyed by the change Id at the start of that batch.
+ * When the delta factory requests a delta, it specifies the start generation, and
+ * this class assembles the deltas for all generations between then and the most
+ * recent delta.
+ */
+class MarkerDeltaManager {
+ private static final int DEFAULT_SIZE = 10;
+ private long[] startIds = new long[DEFAULT_SIZE];
+ @SuppressWarnings("unchecked")
+ private Map[] batches = new Map[DEFAULT_SIZE];
+ private int nextFree = 0;
+
+ /**
+ * Returns the deltas from the given start id up until the present. Returns null
+ * if there are no deltas for that interval.
+ */
+ protected Map assembleDeltas(long start) {
+ Map result = null;
+ for (int i = 0; i < nextFree; i++)
+ if (startIds[i] >= start)
+ result = MarkerDelta.merge(result, batches[i]);
+ return result;
+ }
+
+ /**
+ * Flushes all delta batches up to but not including the given start Id.
+ */
+ @SuppressWarnings("unchecked")
+ protected void resetDeltas(long startId) {
+ //find offset of first batch to keep
+ int startOffset = 0;
+ for (; startOffset < nextFree; startOffset++)
+ if (startIds[startOffset] >= startId)
+ break;
+ if (startOffset == 0)
+ return;
+ long[] newIds = startIds;
+ Map[] newBatches = batches;
+ //shrink the arrays if it has grown too large
+ if (startIds.length > DEFAULT_SIZE && (nextFree - startOffset < DEFAULT_SIZE)) {
+ newIds = new long[DEFAULT_SIZE];
+ newBatches = new Map[DEFAULT_SIZE];
+ }
+ //copy and compact into the new array
+ int remaining = nextFree - startOffset;
+ System.arraycopy(startIds, startOffset, newIds, 0, remaining);
+ System.arraycopy(batches, startOffset, newBatches, 0, remaining);
+ //clear the end of the array
+ Arrays.fill(startIds, remaining, startIds.length, 0);
+ Arrays.fill(batches, remaining, startIds.length, null);
+ startIds = newIds;
+ batches = newBatches;
+ nextFree = remaining;
+ }
+
+ @SuppressWarnings("unchecked")
+ protected Map newGeneration(long start) {
+ int len = startIds.length;
+ if (nextFree >= len) {
+ long[] newIds = new long[len * 2];
+ Map[] newBatches = new Map[len * 2];
+ System.arraycopy(startIds, 0, newIds, 0, len);
+ System.arraycopy(batches, 0, newBatches, 0, len);
+ startIds = newIds;
+ batches = newBatches;
+ }
+ startIds[nextFree] = start;
+ batches[nextFree] = new HashMap(11);
+ return batches[nextFree++];
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerInfo.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerInfo.java
new file mode 100644
index 0000000000..8b36801342
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerInfo.java
@@ -0,0 +1,209 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2015 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * James Blackburn (Broadcom Corp.) - ongoing development
+ *******************************************************************************/
+package org.eclipse.core.internal.resources;
+
+import java.io.UnsupportedEncodingException;
+import java.util.Iterator;
+import java.util.Map;
+import org.eclipse.core.internal.utils.*;
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.osgi.util.NLS;
+
+public class MarkerInfo implements IMarkerSetElement, Cloneable, IStringPoolParticipant {
+
+ // well known Integer values
+ protected static final Integer INTEGER_ONE = new Integer(1);
+ protected static final Integer INTEGER_TWO = new Integer(2);
+ protected static final Integer INTEGER_ZERO = new Integer(0);
+
+ //
+ protected static final long UNDEFINED_ID = -1;
+ /** The store of attributes for this marker. */
+ protected Map attributes = null;
+
+ /** The creation time for this marker. */
+ protected long creationTime = 0;
+
+ /** Marker identifier. */
+ protected long id = UNDEFINED_ID;
+
+ /** The type of this marker. */
+ protected String type = null;
+
+ /**
+ * Returns whether the given object is a valid attribute value. Returns
+ * either the attribute or an equal canonical substitute.
+ */
+ protected static Object checkValidAttribute(Object value) {
+ if (value == null)
+ return null;
+ if (value instanceof String) {
+ //we cannot write attributes whose UTF encoding exceeds 65535 bytes.
+ String valueString = (String) value;
+ //optimized test based on maximum 3 bytes per character
+ if (valueString.length() < 21000)
+ return value;
+ byte[] bytes;
+ try {
+ bytes = valueString.getBytes(("UTF-8"));//$NON-NLS-1$
+ } catch (UnsupportedEncodingException uee) {
+ //cannot validate further
+ return value;
+ }
+ if (bytes.length > 65535) {
+ String msg = "Marker property value is too long: " + valueString.substring(0, 10000); //$NON-NLS-1$
+ Assert.isTrue(false, msg);
+ }
+ return value;
+ }
+ if (value instanceof Boolean) {
+ //return canonical boolean
+ return ((Boolean) value).booleanValue() ? Boolean.TRUE : Boolean.FALSE;
+ }
+ if (value instanceof Integer) {
+ //replace common integers with canonical values
+ switch (((Integer) value).intValue()) {
+ case 0 :
+ return INTEGER_ZERO;
+ case 1 :
+ return INTEGER_ONE;
+ case 2 :
+ return INTEGER_TWO;
+ }
+ return value;
+ }
+ //if we got here, it's an invalid attribute value type
+ throw new IllegalArgumentException(NLS.bind(Messages.resources_wrongMarkerAttributeValueType, value.getClass().getName()));
+ }
+
+ public MarkerInfo() {
+ super();
+ }
+
+ /**
+ * See Object#clone.
+ */
+ @Override
+ public Object clone() {
+ try {
+ MarkerInfo copy = (MarkerInfo) super.clone();
+ //copy the attribute table contents
+ copy.attributes = getAttributes(true);
+ return copy;
+ } catch (CloneNotSupportedException e) {
+ //cannot happen because this class implements Cloneable
+ return null;
+ }
+ }
+
+ public Object getAttribute(String attributeName) {
+ return attributes == null ? null : attributes.get(attributeName);
+ }
+
+ public Map getAttributes() {
+ return getAttributes(true);
+ }
+
+ public Map getAttributes(boolean makeCopy) {
+ if (attributes == null)
+ return null;
+ return makeCopy ? new MarkerAttributeMap(attributes) : attributes;
+ }
+
+ public Object[] getAttributes(String[] attributeNames) {
+ Object[] result = new Object[attributeNames.length];
+ for (int i = 0; i < attributeNames.length; i++)
+ result[i] = getAttribute(attributeNames[i]);
+ return result;
+ }
+
+ public long getCreationTime() {
+ return creationTime;
+ }
+
+ @Override
+ public long getId() {
+ return id;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void internalSetAttributes(Map map) {
+ //the cast effectively acts as an assertion to make sure
+ //the right kind of map is being used
+ attributes = map;
+ }
+
+ public void setAttribute(String attributeName, Object value, boolean validate) {
+ if (validate)
+ value = checkValidAttribute(value);
+ if (attributes == null) {
+ if (value == null)
+ return;
+ attributes = new MarkerAttributeMap();
+ attributes.put(attributeName, value);
+ } else {
+ if (value == null) {
+ attributes.remove(attributeName);
+ if (attributes.isEmpty())
+ attributes = null;
+ } else {
+ attributes.put(attributeName, value);
+ }
+ }
+ }
+
+ public void setAttributes(Map map, boolean validate) {
+ if (map == null)
+ attributes = null;
+ else {
+ attributes = new MarkerAttributeMap(map.size());
+ for (Iterator i = map.keySet().iterator(); i.hasNext();) {
+ Object key = i.next();
+ Assert.isTrue(key instanceof String);
+ Object value = map.get(key);
+ setAttribute((String) key, value, validate);
+ }
+ }
+ }
+
+ public void setAttributes(String[] attributeNames, Object[] values, boolean validate) {
+ Assert.isTrue(attributeNames.length == values.length);
+ for (int i = 0; i < attributeNames.length; i++)
+ setAttribute(attributeNames[i], values[i], validate);
+ }
+
+ public void setCreationTime(long value) {
+ creationTime = value;
+ }
+
+ public void setId(long value) {
+ id = value;
+ }
+
+ public void setType(String value) {
+ type = value;
+ }
+
+ /* (non-Javadoc
+ * Method declared on IStringPoolParticipant
+ */
+ @Override
+ public void shareStrings(StringPool set) {
+ type = set.add(type);
+ Map map = attributes;
+ if (map instanceof IStringPoolParticipant)
+ ((IStringPoolParticipant) map).shareStrings(set);
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerManager.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerManager.java
new file mode 100644
index 0000000000..a635d6b9fb
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerManager.java
@@ -0,0 +1,674 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2015 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * James Blackburn (Broadcom Corp.) - ongoing development
+ *******************************************************************************/
+package org.eclipse.core.internal.resources;
+
+import java.io.*;
+import java.util.*;
+import org.eclipse.core.internal.localstore.SafeChunkyInputStream;
+import org.eclipse.core.internal.localstore.SafeFileInputStream;
+import org.eclipse.core.internal.utils.Messages;
+import org.eclipse.core.internal.utils.Policy;
+import org.eclipse.core.internal.watson.*;
+import org.eclipse.core.resources.*;
+import org.eclipse.core.runtime.*;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * A marker manager stores and retrieves markers on resources in the workspace.
+ */
+public class MarkerManager implements IManager {
+
+ //singletons
+ private static final MarkerInfo[] NO_MARKER_INFO = new MarkerInfo[0];
+ private static final IMarker[] NO_MARKERS = new IMarker[0];
+ protected MarkerTypeDefinitionCache cache = new MarkerTypeDefinitionCache();
+ private long changeId = 0;
+ protected Map currentDeltas = null;
+ protected final MarkerDeltaManager deltaManager = new MarkerDeltaManager();
+
+ protected Workspace workspace;
+ protected MarkerWriter writer = new MarkerWriter(this);
+
+ /**
+ * Creates a new marker manager
+ */
+ public MarkerManager(Workspace workspace) {
+ this.workspace = workspace;
+ }
+
+ /* (non-Javadoc)
+ * Adds the given markers to the given resource.
+ *
+ * @see IResource#createMarker(String)
+ */
+ public void add(IResource resource, MarkerInfo newMarker) throws CoreException {
+ Resource target = (Resource) resource;
+ ResourceInfo info = workspace.getResourceInfo(target.getFullPath(), false, false);
+ target.checkExists(target.getFlags(info), false);
+ info = workspace.getResourceInfo(resource.getFullPath(), false, true);
+ //resource may have been deleted concurrently -- just bail out if this happens
+ if (info == null)
+ return;
+ // set the M_MARKERS_SNAP_DIRTY flag to indicate that this
+ // resource's markers have changed since the last snapshot
+ if (isPersistent(newMarker))
+ info.set(ICoreConstants.M_MARKERS_SNAP_DIRTY);
+ //Concurrency: copy the marker set on modify
+ MarkerSet markers = info.getMarkers(true);
+ if (markers == null)
+ markers = new MarkerSet(1);
+ basicAdd(resource, markers, newMarker);
+ if (!markers.isEmpty())
+ info.setMarkers(markers);
+ }
+
+ /**
+ * Adds the new markers to the given set of markers. If added, the markers
+ * are associated with the specified resource.IMarkerDeltas for Added markers
+ * are generated.
+ */
+ private void basicAdd(IResource resource, MarkerSet markers, MarkerInfo newMarker) throws CoreException {
+ // should always be a new marker.
+ if (newMarker.getId() != MarkerInfo.UNDEFINED_ID) {
+ String message = Messages.resources_changeInAdd;
+ throw new ResourceException(new ResourceStatus(IResourceStatus.INTERNAL_ERROR, resource.getFullPath(), message));
+ }
+ newMarker.setId(workspace.nextMarkerId());
+ markers.add(newMarker);
+ IMarkerSetElement[] changes = new IMarkerSetElement[1];
+ changes[0] = new MarkerDelta(IResourceDelta.ADDED, resource, newMarker);
+ changedMarkers(resource, changes);
+ }
+
+ /**
+ * Returns the markers in the given set of markers which match the given type.
+ */
+ protected MarkerInfo[] basicFindMatching(MarkerSet markers, String type, boolean includeSubtypes) {
+ int size = markers.size();
+ if (size <= 0)
+ return NO_MARKER_INFO;
+ List result = new ArrayList(size);
+ IMarkerSetElement[] elements = markers.elements();
+ for (int i = 0; i < elements.length; i++) {
+ MarkerInfo marker = (MarkerInfo) elements[i];
+ // if the type is null then we are looking for all types of markers
+ if (type == null)
+ result.add(marker);
+ else {
+ if (includeSubtypes) {
+ if (cache.isSubtype(marker.getType(), type))
+ result.add(marker);
+ } else {
+ if (marker.getType().equals(type))
+ result.add(marker);
+ }
+ }
+ }
+ size = result.size();
+ if (size <= 0)
+ return NO_MARKER_INFO;
+ return result.toArray(new MarkerInfo[size]);
+ }
+
+ protected int basicFindMaxSeverity(MarkerSet markers, String type, boolean includeSubtypes) {
+ int max = -1;
+ int size = markers.size();
+ if (size <= 0)
+ return max;
+ IMarkerSetElement[] elements = markers.elements();
+ for (int i = 0; i < elements.length; i++) {
+ MarkerInfo marker = (MarkerInfo) elements[i];
+ // if the type is null then we are looking for all types of markers
+ if (type == null)
+ max = Math.max(max, getSeverity(marker));
+ else {
+ if (includeSubtypes) {
+ if (cache.isSubtype(marker.getType(), type))
+ max = Math.max(max, getSeverity(marker));
+ } else {
+ if (marker.getType().equals(type))
+ max = Math.max(max, getSeverity(marker));
+ }
+ }
+ if (max >= IMarker.SEVERITY_ERROR) {
+ break;
+ }
+ }
+ return max;
+ }
+
+ private int getSeverity(MarkerInfo marker) {
+ Object o = marker.getAttribute(IMarker.SEVERITY);
+ if (o instanceof Integer) {
+ Integer i = (Integer) o;
+ return i.intValue();
+ }
+ return -1;
+ }
+
+ /**
+ * Removes markers of the specified type from the given resource.
+ * Note: this method is protected to avoid creation of a synthetic accessor (it
+ * is called from an anonymous inner class).
+ */
+ protected void basicRemoveMarkers(ResourceInfo info, IPathRequestor requestor, String type, boolean includeSubtypes) {
+ MarkerSet markers = info.getMarkers(false);
+ if (markers == null)
+ return;
+ IMarkerSetElement[] matching;
+ IPath path;
+ if (type == null) {
+ // if the type is null, all markers are to be removed.
+ //now we need to crack open the tree
+ path = requestor.requestPath();
+ info = workspace.getResourceInfo(path, false, true);
+ info.setMarkers(null);
+ matching = markers.elements();
+ } else {
+ matching = basicFindMatching(markers, type, includeSubtypes);
+ // if none match, there is nothing to remove
+ if (matching.length == 0)
+ return;
+ //now we need to crack open the tree
+ path = requestor.requestPath();
+ info = workspace.getResourceInfo(path, false, true);
+ //Concurrency: copy the marker set on modify
+ markers = info.getMarkers(true);
+ // remove all the matching markers and also the whole
+ // set if there are no remaining markers
+ if (markers.size() == matching.length) {
+ info.setMarkers(null);
+ } else {
+ markers.removeAll(matching);
+ info.setMarkers(markers);
+ }
+ }
+ info.set(ICoreConstants.M_MARKERS_SNAP_DIRTY);
+ IMarkerSetElement[] changes = new IMarkerSetElement[matching.length];
+ IResource resource = workspace.getRoot().findMember(path);
+ for (int i = 0; i < matching.length; i++)
+ changes[i] = new MarkerDelta(IResourceDelta.REMOVED, resource, (MarkerInfo) matching[i]);
+ changedMarkers(resource, changes);
+ return;
+ }
+
+ /**
+ * Adds the markers on the given target which match the specified type to the list.
+ */
+ protected void buildMarkers(IMarkerSetElement[] markers, IPath path, int type, ArrayList list) {
+ if (markers.length == 0)
+ return;
+ IResource resource = workspace.newResource(path, type);
+ list.ensureCapacity(list.size() + markers.length);
+ for (int i = 0; i < markers.length; i++) {
+ list.add(new Marker(resource, ((MarkerInfo) markers[i]).getId()));
+ }
+ }
+
+ /**
+ * Markers have changed on the given resource. Remember the changes for subsequent notification.
+ */
+ protected void changedMarkers(IResource resource, IMarkerSetElement[] changes) {
+ if (changes == null || changes.length == 0)
+ return;
+ changeId++;
+ if (currentDeltas == null)
+ currentDeltas = deltaManager.newGeneration(changeId);
+ IPath path = resource.getFullPath();
+ MarkerSet previousChanges = currentDeltas.get(path);
+ MarkerSet result = MarkerDelta.merge(previousChanges, changes);
+ if (result.size() == 0)
+ currentDeltas.remove(path);
+ else
+ currentDeltas.put(path, result);
+ ResourceInfo info = workspace.getResourceInfo(path, false, true);
+ if (info != null)
+ info.incrementMarkerGenerationCount();
+ }
+
+ /**
+ * Returns the marker with the given id or null if none is found.
+ */
+ public IMarker findMarker(IResource resource, long id) {
+ MarkerInfo info = findMarkerInfo(resource, id);
+ return info == null ? null : new Marker(resource, info.getId());
+ }
+
+ /**
+ * Returns the marker with the given id or null if none is found.
+ */
+ public MarkerInfo findMarkerInfo(IResource resource, long id) {
+ ResourceInfo info = workspace.getResourceInfo(resource.getFullPath(), false, false);
+ if (info == null)
+ return null;
+ MarkerSet markers = info.getMarkers(false);
+ if (markers == null)
+ return null;
+ return (MarkerInfo) markers.get(id);
+ }
+
+ /**
+ * Returns all markers of the specified type on the given target, with option
+ * to search the target's children.
+ * Passing null for the type specifies a match
+ * for all types (i.e., null is a wildcard.
+ */
+ public IMarker[] findMarkers(IResource target, final String type, final boolean includeSubtypes, int depth) {
+ ArrayList result = new ArrayList();
+ doFindMarkers(target, result, type, includeSubtypes, depth);
+ if (result.size() == 0)
+ return NO_MARKERS;
+ return result.toArray(new IMarker[result.size()]);
+ }
+
+ /**
+ * Fills the provided list with all markers of the specified type on the given target,
+ * with option to search the target's children.
+ * Passing null for the type specifies a match
+ * for all types (i.e., null is a wildcard.
+ */
+ public void doFindMarkers(IResource target, ArrayList result, final String type, final boolean includeSubtypes, int depth) {
+ //optimize the deep searches with an element tree visitor
+ if (depth == IResource.DEPTH_INFINITE && target.getType() != IResource.FILE)
+ visitorFindMarkers(target.getFullPath(), result, type, includeSubtypes);
+ else
+ recursiveFindMarkers(target.getFullPath(), result, type, includeSubtypes, depth);
+ }
+
+ /**
+ * Finds the max severity across all problem markers on the given target,
+ * with option to search the target's children.
+ */
+ public int findMaxProblemSeverity(IResource target, String type, boolean includeSubtypes, int depth) {
+ //optimize the deep searches with an element tree visitor
+ if (depth == IResource.DEPTH_INFINITE && target.getType() != IResource.FILE)
+ return visitorFindMaxSeverity(target.getFullPath(), type, includeSubtypes);
+ return recursiveFindMaxSeverity(target.getFullPath(), type, includeSubtypes, depth);
+ }
+
+ public long getChangeId() {
+ return changeId;
+ }
+
+ /**
+ * Returns the map of all marker deltas since the given change Id.
+ */
+ public Map getMarkerDeltas(long startChangeId) {
+ return deltaManager.assembleDeltas(startChangeId);
+ }
+
+ /**
+ * Returns true if this manager has a marker delta record
+ * for the given marker id, and false otherwise.
+ */
+ boolean hasDelta(IPath path, long id) {
+ if (currentDeltas == null)
+ return false;
+ MarkerSet set = currentDeltas.get(path);
+ if (set == null)
+ return false;
+ return set.get(id) != null;
+ }
+
+ /**
+ * Returns true if the given marker is persistent, and false
+ * otherwise.
+ */
+ public boolean isPersistent(MarkerInfo info) {
+ if (!cache.isPersistent(info.getType()))
+ return false;
+ Object isTransient = info.getAttribute(IMarker.TRANSIENT);
+ return isTransient == null || !(isTransient instanceof Boolean) || !((Boolean) isTransient).booleanValue();
+ }
+
+ /**
+ * Returns true if the given marker type is persistent, and false
+ * otherwise.
+ */
+ public boolean isPersistentType(String type) {
+ return cache.isPersistent(type);
+ }
+
+ /**
+ * Returns true if type is a sub type of superType.
+ */
+ public boolean isSubtype(String type, String superType) {
+ return cache.isSubtype(type, superType);
+ }
+
+ public void moved(final IResource source, final IResource destination, int depth) throws CoreException {
+ final int count = destination.getFullPath().segmentCount();
+
+ // we removed from the source and added to the destination
+ IResourceVisitor visitor = new IResourceVisitor() {
+ @Override
+ public boolean visit(IResource resource) {
+ Resource r = (Resource) resource;
+ ResourceInfo info = r.getResourceInfo(false, true);
+ MarkerSet markers = info.getMarkers(false);
+ if (markers == null)
+ return true;
+ info.set(ICoreConstants.M_MARKERS_SNAP_DIRTY);
+ IMarkerSetElement[] removed = new IMarkerSetElement[markers.size()];
+ IMarkerSetElement[] added = new IMarkerSetElement[markers.size()];
+ IPath path = resource.getFullPath().removeFirstSegments(count);
+ path = source.getFullPath().append(path);
+ IResource sourceChild = workspace.newResource(path, resource.getType());
+ IMarkerSetElement[] elements = markers.elements();
+ for (int i = 0; i < elements.length; i++) {
+ // calculate the ADDED delta
+ MarkerInfo markerInfo = (MarkerInfo) elements[i];
+ MarkerDelta delta = new MarkerDelta(IResourceDelta.ADDED, resource, markerInfo);
+ added[i] = delta;
+ // calculate the REMOVED delta
+ delta = new MarkerDelta(IResourceDelta.REMOVED, sourceChild, markerInfo);
+ removed[i] = delta;
+ }
+ changedMarkers(resource, added);
+ changedMarkers(sourceChild, removed);
+ return true;
+ }
+ };
+ destination.accept(visitor, depth, IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS | IContainer.INCLUDE_HIDDEN);
+ }
+
+ /**
+ * Adds the markers for a subtree of resources to the list.
+ */
+ private void recursiveFindMarkers(IPath path, ArrayList list, String type, boolean includeSubtypes, int depth) {
+ ResourceInfo info = workspace.getResourceInfo(path, false, false);
+ if (info == null)
+ return;
+ MarkerSet markers = info.getMarkers(false);
+
+ //add the matching markers for this resource
+ if (markers != null) {
+ IMarkerSetElement[] matching;
+ if (type == null)
+ matching = markers.elements();
+ else
+ matching = basicFindMatching(markers, type, includeSubtypes);
+ buildMarkers(matching, path, info.getType(), list);
+ }
+
+ //recurse
+ if (depth == IResource.DEPTH_ZERO || info.getType() == IResource.FILE)
+ return;
+ if (depth == IResource.DEPTH_ONE)
+ depth = IResource.DEPTH_ZERO;
+ IPath[] children = workspace.getElementTree().getChildren(path);
+ for (int i = 0; i < children.length; i++) {
+ recursiveFindMarkers(children[i], list, type, includeSubtypes, depth);
+ }
+ }
+
+ /**
+ * Finds the max severity across problem markers for a subtree of resources.
+ */
+ private int recursiveFindMaxSeverity(IPath path, String type, boolean includeSubtypes, int depth) {
+ ResourceInfo info = workspace.getResourceInfo(path, false, false);
+ if (info == null)
+ return -1;
+ MarkerSet markers = info.getMarkers(false);
+
+ //add the matching markers for this resource
+ int max = -1;
+ if (markers != null) {
+ max = basicFindMaxSeverity(markers, type, includeSubtypes);
+ if (max >= IMarker.SEVERITY_ERROR) {
+ return max;
+ }
+ }
+
+ //recurse
+ if (depth == IResource.DEPTH_ZERO || info.getType() == IResource.FILE)
+ return max;
+ if (depth == IResource.DEPTH_ONE)
+ depth = IResource.DEPTH_ZERO;
+ IPath[] children = workspace.getElementTree().getChildren(path);
+ for (int i = 0; i < children.length; i++) {
+ max = Math.max(max, recursiveFindMaxSeverity(children[i], type, includeSubtypes, depth));
+ if (max >= IMarker.SEVERITY_ERROR) {
+ break;
+ }
+ }
+ return max;
+ }
+
+ /**
+ * Adds the markers for a subtree of resources to the list.
+ */
+ private void recursiveRemoveMarkers(final IPath path, String type, boolean includeSubtypes, int depth) {
+ ResourceInfo info = workspace.getResourceInfo(path, false, false);
+ if (info == null) //phantoms don't have markers
+ return;
+ IPathRequestor requestor = new IPathRequestor() {
+ @Override
+ public String requestName() {
+ return path.lastSegment();
+ }
+
+ @Override
+ public IPath requestPath() {
+ return path;
+ }
+ };
+ basicRemoveMarkers(info, requestor, type, includeSubtypes);
+ //recurse
+ if (depth == IResource.DEPTH_ZERO || info.getType() == IResource.FILE)
+ return;
+ if (depth == IResource.DEPTH_ONE)
+ depth = IResource.DEPTH_ZERO;
+ IPath[] children = workspace.getElementTree().getChildren(path);
+ for (int i = 0; i < children.length; i++) {
+ recursiveRemoveMarkers(children[i], type, includeSubtypes, depth);
+ }
+ }
+
+ /**
+ * Removes the specified marker
+ */
+ public void removeMarker(IResource resource, long id) {
+ MarkerInfo markerInfo = findMarkerInfo(resource, id);
+ if (markerInfo == null)
+ return;
+ ResourceInfo info = ((Workspace) resource.getWorkspace()).getResourceInfo(resource.getFullPath(), false, true);
+ //Concurrency: copy the marker set on modify
+ MarkerSet markers = info.getMarkers(true);
+ int size = markers.size();
+ markers.remove(markerInfo);
+ // if that was the last marker remove the set to save space.
+ info.setMarkers(markers.size() == 0 ? null : markers);
+ // if we actually did remove a marker, post a delta for the change.
+ if (markers.size() != size) {
+ if (isPersistent(markerInfo))
+ info.set(ICoreConstants.M_MARKERS_SNAP_DIRTY);
+ IMarkerSetElement[] change = new IMarkerSetElement[] {new MarkerDelta(IResourceDelta.REMOVED, resource, markerInfo)};
+ changedMarkers(resource, change);
+ }
+ }
+
+ /**
+ * Remove all markers for the given resource to the specified depth.
+ */
+ public void removeMarkers(IResource resource, int depth) {
+ removeMarkers(resource, null, false, depth);
+ }
+
+ /**
+ * Remove all markers with the given type from the node at the given path.
+ * Passing null for the type specifies a match
+ * for all types (i.e., null is a wildcard.
+ */
+ public void removeMarkers(IResource target, final String type, final boolean includeSubtypes, int depth) {
+ if (depth == IResource.DEPTH_INFINITE && target.getType() != IResource.FILE)
+ visitorRemoveMarkers(target.getFullPath(), type, includeSubtypes);
+ else
+ recursiveRemoveMarkers(target.getFullPath(), type, includeSubtypes, depth);
+ }
+
+ /**
+ * Reset the marker deltas up to but not including the given start Id.
+ */
+ public void resetMarkerDeltas(long startId) {
+ currentDeltas = null;
+ deltaManager.resetDeltas(startId);
+ }
+
+ public void restore(IResource resource, boolean generateDeltas, IProgressMonitor monitor) throws CoreException {
+ // first try and load the last saved file, then apply the snapshots
+ restoreFromSave(resource, generateDeltas);
+ restoreFromSnap(resource);
+ }
+
+ protected void restoreFromSave(IResource resource, boolean generateDeltas) throws CoreException {
+ IPath sourceLocation = workspace.getMetaArea().getMarkersLocationFor(resource);
+ IPath tempLocation = workspace.getMetaArea().getBackupLocationFor(sourceLocation);
+ java.io.File sourceFile = new java.io.File(sourceLocation.toOSString());
+ java.io.File tempFile = new java.io.File(tempLocation.toOSString());
+ if (!sourceFile.exists() && !tempFile.exists())
+ return;
+ try {
+ DataInputStream input = new DataInputStream(new SafeFileInputStream(sourceLocation.toOSString(), tempLocation.toOSString()));
+ try {
+ MarkerReader reader = new MarkerReader(workspace);
+ reader.read(input, generateDeltas);
+ } finally {
+ input.close();
+ }
+ } catch (Exception e) {
+ //don't let runtime exceptions such as ArrayIndexOutOfBounds prevent startup
+ String msg = NLS.bind(Messages.resources_readMeta, sourceLocation);
+ throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, sourceLocation, msg, e);
+ }
+ }
+
+ protected void restoreFromSnap(IResource resource) {
+ IPath sourceLocation = workspace.getMetaArea().getMarkersSnapshotLocationFor(resource);
+ if (!sourceLocation.toFile().exists())
+ return;
+ try {
+ DataInputStream input = new DataInputStream(new SafeChunkyInputStream(sourceLocation.toFile()));
+ try {
+ MarkerSnapshotReader reader = new MarkerSnapshotReader(workspace);
+ while (true)
+ reader.read(input);
+ } catch (EOFException eof) {
+ // ignore end of file
+ } finally {
+ input.close();
+ }
+ } catch (Exception e) {
+ // only log the exception, we should not fail restoring the snapshot
+ String msg = NLS.bind(Messages.resources_readMeta, sourceLocation);
+ Policy.log(new ResourceStatus(IResourceStatus.FAILED_READ_METADATA, sourceLocation, msg, e));
+ }
+ }
+
+ public void save(ResourceInfo info, IPathRequestor requestor, DataOutputStream output, List list) throws IOException {
+ writer.save(info, requestor, output, list);
+ }
+
+ /* (non-Javadoc)
+ * @see IManager#shutdown(IProgressMonitor)
+ */
+ @Override
+ public void shutdown(IProgressMonitor monitor) {
+ // do nothing
+ }
+
+ public void snap(ResourceInfo info, IPathRequestor requestor, DataOutputStream output) throws IOException {
+ writer.snap(info, requestor, output);
+ }
+
+ /* (non-Javadoc)
+ * @see IManager#startup(IProgressMonitor)
+ */
+ @Override
+ public void startup(IProgressMonitor monitor) {
+ // do nothing
+ }
+
+ /**
+ * Adds the markers for a subtree of resources to the list.
+ */
+ private void visitorFindMarkers(IPath path, final ArrayList list, final String type, final boolean includeSubtypes) {
+ IElementContentVisitor visitor = new IElementContentVisitor() {
+ @Override
+ public boolean visitElement(ElementTree tree, IPathRequestor requestor, Object elementContents) {
+ ResourceInfo info = (ResourceInfo) elementContents;
+ if (info == null)
+ return false;
+ MarkerSet markers = info.getMarkers(false);
+
+ //add the matching markers for this resource
+ if (markers != null) {
+ IMarkerSetElement[] matching;
+ if (type == null)
+ matching = markers.elements();
+ else
+ matching = basicFindMatching(markers, type, includeSubtypes);
+ buildMarkers(matching, requestor.requestPath(), info.getType(), list);
+ }
+ return true;
+ }
+ };
+ new ElementTreeIterator(workspace.getElementTree(), path).iterate(visitor);
+ }
+
+ /**
+ * Finds the max severity across problem markers for a subtree of resources.
+ */
+ private int visitorFindMaxSeverity(IPath path, final String type, final boolean includeSubtypes) {
+ class MaxSeverityVisitor implements IElementContentVisitor {
+ int max = -1;
+
+ @Override
+ public boolean visitElement(ElementTree tree, IPathRequestor requestor, Object elementContents) {
+ // bail if an earlier sibling already hit the max
+ if (max >= IMarker.SEVERITY_ERROR) {
+ return false;
+ }
+ ResourceInfo info = (ResourceInfo) elementContents;
+ if (info == null)
+ return false;
+ MarkerSet markers = info.getMarkers(false);
+
+ //add the matching markers for this resource
+ if (markers != null) {
+ max = Math.max(max, basicFindMaxSeverity(markers, type, includeSubtypes));
+ }
+ return max < IMarker.SEVERITY_ERROR;
+ }
+ }
+ MaxSeverityVisitor visitor = new MaxSeverityVisitor();
+ new ElementTreeIterator(workspace.getElementTree(), path).iterate(visitor);
+ return visitor.max;
+ }
+
+ /**
+ * Adds the markers for a subtree of resources to the list.
+ */
+ private void visitorRemoveMarkers(IPath path, final String type, final boolean includeSubtypes) {
+ IElementContentVisitor visitor = new IElementContentVisitor() {
+ @Override
+ public boolean visitElement(ElementTree tree, IPathRequestor requestor, Object elementContents) {
+ ResourceInfo info = (ResourceInfo) elementContents;
+ if (info == null)
+ return false;
+ basicRemoveMarkers(info, requestor, type, includeSubtypes);
+ return true;
+ }
+ };
+ new ElementTreeIterator(workspace.getElementTree(), path).iterate(visitor);
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerReader.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerReader.java
new file mode 100644
index 0000000000..1aec78fc15
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerReader.java
@@ -0,0 +1,56 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.resources;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+import org.eclipse.core.internal.utils.Messages;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * This class is used to read markers from disk. Subclasses implement
+ * version specific reading code.
+ */
+public class MarkerReader {
+ protected Workspace workspace;
+
+ public MarkerReader(Workspace workspace) {
+ super();
+ this.workspace = workspace;
+ }
+
+ /**
+ * Returns the appropriate reader for the given version.
+ */
+ protected MarkerReader getReader(int formatVersion) throws IOException {
+ switch (formatVersion) {
+ case 1 :
+ return new MarkerReader_1(workspace);
+ case 2 :
+ return new MarkerReader_2(workspace);
+ case 3 :
+ return new MarkerReader_3(workspace);
+ default :
+ throw new IOException(NLS.bind(Messages.resources_format, new Integer(formatVersion)));
+ }
+ }
+
+ public void read(DataInputStream input, boolean generateDeltas) throws IOException, CoreException {
+ int formatVersion = readVersionNumber(input);
+ MarkerReader reader = getReader(formatVersion);
+ reader.read(input, generateDeltas);
+ }
+
+ protected static int readVersionNumber(DataInputStream input) throws IOException {
+ return input.readInt();
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerReader_1.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerReader_1.java
new file mode 100644
index 0000000000..f22fb8a072
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerReader_1.java
@@ -0,0 +1,144 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2014 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * James Blackburn (Broadcom Corp.) - ongoing development
+ *******************************************************************************/
+package org.eclipse.core.internal.resources;
+
+import java.io.*;
+import java.util.*;
+import org.eclipse.core.internal.utils.Messages;
+import org.eclipse.core.resources.IResourceDelta;
+import org.eclipse.core.resources.IResourceStatus;
+import org.eclipse.core.runtime.*;
+
+/**
+ * This class is used to read markers from disk. This is for version 1.
+ */
+public class MarkerReader_1 extends MarkerReader {
+
+ // type constants
+ public static final int INDEX = 1;
+ public static final int QNAME = 2;
+
+ // marker attribute types
+ public static final int ATTRIBUTE_NULL = -1;
+ public static final int ATTRIBUTE_BOOLEAN = 0;
+ public static final int ATTRIBUTE_INTEGER = 1;
+ public static final int ATTRIBUTE_STRING = 2;
+
+ public MarkerReader_1(Workspace workspace) {
+ super(workspace);
+ }
+
+ /**
+ * SAVE_FILE -> VERSION_ID RESOURCE+
+ * VERSION_ID ->
+ * RESOURCE -> RESOURCE_PATH MARKERS_SIZE MARKER*
+ * RESOURCE_PATH -> String
+ * MARKERS_SIZE -> int
+ * MARKER -> MARKER_ID TYPE ATTRIBUTES_SIZE ATTRIBUTE*
+ * MARKER_ID -> long
+ * TYPE -> INDEX | QNAME
+ * INDEX -> int int
+ * QNAME -> int String
+ * ATTRIBUTES_SIZE -> int
+ * ATTRIBUTE -> ATTRIBUTE_KEY ATTRIBUTE_VALUE
+ * ATTRIBUTE_KEY -> String
+ * ATTRIBUTE_VALUE -> INTEGER_VALUE | BOOLEAN_VALUE | STRING_VALUE | NULL_VALUE
+ * INTEGER_VALUE -> int int
+ * BOOLEAN_VALUE -> int boolean
+ * STRING_VALUE -> int String
+ * NULL_VALUE -> int
+ */
+ @Override
+ public void read(DataInputStream input, boolean generateDeltas) throws IOException, CoreException {
+ try {
+ List readTypes = new ArrayList(5);
+ while (true) {
+ IPath path = new Path(input.readUTF());
+ int markersSize = input.readInt();
+ MarkerSet markers = new MarkerSet(markersSize);
+ for (int i = 0; i < markersSize; i++)
+ markers.add(readMarkerInfo(input, readTypes));
+ // if the resource doesn't exist then return. ensure we do this after
+ // reading the markers from the file so we don't get into an
+ // inconsistent state.
+ ResourceInfo info = workspace.getResourceInfo(path, false, false);
+ if (info == null)
+ continue;
+ info.setMarkers(markers);
+ if (generateDeltas) {
+ Resource resource = workspace.newResource(path, info.getType());
+ // Iterate over all elements and add not null ones. This saves us from copying
+ // and shrinking the array.
+ IMarkerSetElement[] infos = markers.elements;
+ ArrayList deltas = new ArrayList(infos.length);
+ for (int i = 0; i < infos.length; i++)
+ if (infos[i] != null)
+ deltas.add(new MarkerDelta(IResourceDelta.ADDED, resource, (MarkerInfo) infos[i]));
+ workspace.getMarkerManager().changedMarkers(resource, deltas.toArray(new IMarkerSetElement[deltas.size()]));
+ }
+ }
+ } catch (EOFException e) {
+ // ignore end of file
+ }
+ }
+
+ private Map readAttributes(DataInputStream input) throws IOException {
+ int attributesSize = input.readInt();
+ if (attributesSize == 0)
+ return null;
+ Map result = new MarkerAttributeMap(attributesSize);
+ for (int j = 0; j < attributesSize; j++) {
+ String key = input.readUTF();
+ int type = input.readInt();
+ Object value = null;
+ switch (type) {
+ case ATTRIBUTE_INTEGER :
+ value = new Integer(input.readInt());
+ break;
+ case ATTRIBUTE_BOOLEAN :
+ value = input.readBoolean() ? Boolean.TRUE : Boolean.FALSE;
+ break;
+ case ATTRIBUTE_STRING :
+ value = input.readUTF();
+ break;
+ case ATTRIBUTE_NULL :
+ // do nothing
+ break;
+ }
+ if (value != null)
+ result.put(key, value);
+ }
+ return result.isEmpty() ? null : result;
+ }
+
+ private MarkerInfo readMarkerInfo(DataInputStream input, List readTypes) throws IOException, CoreException {
+ MarkerInfo info = new MarkerInfo();
+ info.setId(input.readLong());
+ int constant = input.readInt();
+ switch (constant) {
+ case QNAME :
+ String type = input.readUTF();
+ info.setType(type);
+ readTypes.add(type);
+ break;
+ case INDEX :
+ info.setType(readTypes.get(input.readInt()));
+ break;
+ default :
+ //if we get here the marker file is corrupt
+ String msg = Messages.resources_readMarkers;
+ throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, null, msg, null);
+ }
+ info.internalSetAttributes(readAttributes(input));
+ return info;
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerReader_2.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerReader_2.java
new file mode 100644
index 0000000000..4c1c04790b
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerReader_2.java
@@ -0,0 +1,145 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2014 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * James Blackburn (Broadcom Corp.) - ongoing development
+ *******************************************************************************/
+package org.eclipse.core.internal.resources;
+
+import java.io.*;
+import java.util.*;
+import org.eclipse.core.internal.utils.Messages;
+import org.eclipse.core.resources.IResourceDelta;
+import org.eclipse.core.resources.IResourceStatus;
+import org.eclipse.core.runtime.*;
+
+/**
+ * This class is used to read markers from disk. This is for version 2. Here
+ * is the file format:
+ */
+public class MarkerReader_2 extends MarkerReader {
+
+ // type constants
+ public static final byte INDEX = 1;
+ public static final byte QNAME = 2;
+
+ // marker attribute types
+ public static final byte ATTRIBUTE_NULL = 0;
+ public static final byte ATTRIBUTE_BOOLEAN = 1;
+ public static final byte ATTRIBUTE_INTEGER = 2;
+ public static final byte ATTRIBUTE_STRING = 3;
+
+ public MarkerReader_2(Workspace workspace) {
+ super(workspace);
+ }
+
+ /**
+ * SAVE_FILE -> VERSION_ID RESOURCE+
+ * VERSION_ID -> int
+ * RESOURCE -> RESOURCE_PATH MARKERS_SIZE MARKER+
+ * RESOURCE_PATH -> String
+ * MARKERS_SIZE -> int
+ * MARKER -> MARKER_ID TYPE ATTRIBUTES_SIZE ATTRIBUTE*
+ * MARKER_ID -> long
+ * TYPE -> INDEX | QNAME
+ * INDEX -> byte int
+ * QNAME -> byte String
+ * ATTRIBUTES_SIZE -> short
+ * ATTRIBUTE -> ATTRIBUTE_KEY ATTRIBUTE_VALUE
+ * ATTRIBUTE_KEY -> String
+ * ATTRIBUTE_VALUE -> INTEGER_VALUE | BOOLEAN_VALUE | STRING_VALUE | NULL_VALUE
+ * INTEGER_VALUE -> byte int
+ * BOOLEAN_VALUE -> byte boolean
+ * STRING_VALUE -> byte String
+ * NULL_VALUE -> byte
+ */
+ @Override
+ public void read(DataInputStream input, boolean generateDeltas) throws IOException, CoreException {
+ try {
+ List readTypes = new ArrayList(5);
+ while (true) {
+ IPath path = new Path(input.readUTF());
+ int markersSize = input.readInt();
+ MarkerSet markers = new MarkerSet(markersSize);
+ for (int i = 0; i < markersSize; i++)
+ markers.add(readMarkerInfo(input, readTypes));
+ // if the resource doesn't exist then return. ensure we do this after
+ // reading the markers from the file so we don't get into an
+ // inconsistent state.
+ ResourceInfo info = workspace.getResourceInfo(path, false, false);
+ if (info == null)
+ continue;
+ info.setMarkers(markers);
+ if (generateDeltas) {
+ // Iterate over all elements and add not null ones. This saves us from copying
+ // and shrinking the array.
+ Resource resource = workspace.newResource(path, info.getType());
+ IMarkerSetElement[] infos = markers.elements;
+ ArrayList deltas = new ArrayList(infos.length);
+ for (int i = 0; i < infos.length; i++)
+ if (infos[i] != null)
+ deltas.add(new MarkerDelta(IResourceDelta.ADDED, resource, (MarkerInfo) infos[i]));
+ workspace.getMarkerManager().changedMarkers(resource, deltas.toArray(new IMarkerSetElement[deltas.size()]));
+ }
+ }
+ } catch (EOFException e) {
+ // ignore end of file
+ }
+ }
+
+ private Map readAttributes(DataInputStream input) throws IOException {
+ int attributesSize = input.readShort();
+ if (attributesSize == 0)
+ return null;
+ Map result = new MarkerAttributeMap(attributesSize);
+ for (int j = 0; j < attributesSize; j++) {
+ String key = input.readUTF();
+ byte type = input.readByte();
+ Object value = null;
+ switch (type) {
+ case ATTRIBUTE_INTEGER :
+ value = new Integer(input.readInt());
+ break;
+ case ATTRIBUTE_BOOLEAN :
+ value = input.readBoolean() ? Boolean.TRUE : Boolean.FALSE;
+ break;
+ case ATTRIBUTE_STRING :
+ value = input.readUTF();
+ break;
+ case ATTRIBUTE_NULL :
+ // do nothing
+ break;
+ }
+ if (value != null)
+ result.put(key, value);
+ }
+ return result.isEmpty() ? null : result;
+ }
+
+ private MarkerInfo readMarkerInfo(DataInputStream input, List readTypes) throws IOException, CoreException {
+ MarkerInfo info = new MarkerInfo();
+ info.setId(input.readLong());
+ byte constant = input.readByte();
+ switch (constant) {
+ case QNAME :
+ String type = input.readUTF();
+ info.setType(type);
+ readTypes.add(type);
+ break;
+ case INDEX :
+ info.setType(readTypes.get(input.readInt()));
+ break;
+ default :
+ //if we get here the marker file is corrupt
+ String msg = Messages.resources_readMarkers;
+ throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, null, msg, null);
+ }
+ info.internalSetAttributes(readAttributes(input));
+ return info;
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerReader_3.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerReader_3.java
new file mode 100644
index 0000000000..1b3ce97928
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerReader_3.java
@@ -0,0 +1,161 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2014 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * James Blackburn (Broadcom Corp.) - ongoing development
+ *******************************************************************************/
+package org.eclipse.core.internal.resources;
+
+import java.io.*;
+import java.util.*;
+import org.eclipse.core.internal.utils.Messages;
+import org.eclipse.core.resources.IResourceDelta;
+import org.eclipse.core.resources.IResourceStatus;
+import org.eclipse.core.runtime.*;
+
+/**
+ * This class is used to read markers from disk. This is for version 2. Here
+ * is the file format:
+ */
+public class MarkerReader_3 extends MarkerReader {
+
+ // type constants
+ public static final byte INDEX = 1;
+ public static final byte QNAME = 2;
+
+ // marker attribute types
+ public static final byte ATTRIBUTE_NULL = 0;
+ public static final byte ATTRIBUTE_BOOLEAN = 1;
+ public static final byte ATTRIBUTE_INTEGER = 2;
+ public static final byte ATTRIBUTE_STRING = 3;
+
+ public MarkerReader_3(Workspace workspace) {
+ super(workspace);
+ }
+
+ /**
+ * SAVE_FILE -> VERSION_ID RESOURCE+
+ * VERSION_ID -> int
+ * RESOURCE -> RESOURCE_PATH MARKERS_SIZE MARKER+
+ * RESOURCE_PATH -> String
+ * MARKERS_SIZE -> int
+ * MARKER -> MARKER_ID TYPE ATTRIBUTES_SIZE ATTRIBUTE* CREATION_TIME
+ * MARKER_ID -> long
+ * TYPE -> INDEX | QNAME
+ * INDEX -> byte int
+ * QNAME -> byte String
+ * ATTRIBUTES_SIZE -> short
+ * ATTRIBUTE -> ATTRIBUTE_KEY ATTRIBUTE_VALUE
+ * ATTRIBUTE_KEY -> String
+ * ATTRIBUTE_VALUE -> INTEGER_VALUE | BOOLEAN_VALUE | STRING_VALUE | NULL_VALUE
+ * INTEGER_VALUE -> byte int
+ * BOOLEAN_VALUE -> byte boolean
+ * STRING_VALUE -> byte String
+ * NULL_VALUE -> byte
+ * CREATION_TIME -> long
+ */
+ @Override
+ public void read(DataInputStream input, boolean generateDeltas) throws IOException, CoreException {
+ try {
+ List readTypes = new ArrayList(5);
+ while (true) {
+ IPath path = new Path(input.readUTF());
+ int markersSize = input.readInt();
+ MarkerSet markers = new MarkerSet(markersSize);
+ for (int i = 0; i < markersSize; i++)
+ markers.add(readMarkerInfo(input, readTypes));
+ // if the resource doesn't exist then return. ensure we do this after
+ // reading the markers from the file so we don't get into an
+ // inconsistent state.
+ ResourceInfo info = workspace.getResourceInfo(path, false, false);
+ if (info == null)
+ continue;
+ info.setMarkers(markers);
+ if (generateDeltas) {
+ // Iterate over all elements and add not null ones. This saves us from copying
+ // and shrinking the array.
+ Resource resource = workspace.newResource(path, info.getType());
+ IMarkerSetElement[] infos = markers.elements;
+ ArrayList deltas = new ArrayList(infos.length);
+ for (int i = 0; i < infos.length; i++)
+ if (infos[i] != null)
+ deltas.add(new MarkerDelta(IResourceDelta.ADDED, resource, (MarkerInfo) infos[i]));
+ workspace.getMarkerManager().changedMarkers(resource, deltas.toArray(new IMarkerSetElement[deltas.size()]));
+ }
+ }
+ } catch (EOFException e) {
+ // ignore end of file
+ }
+ }
+
+ private Map readAttributes(DataInputStream input) throws IOException {
+ int attributesSize = input.readShort();
+ if (attributesSize == 0)
+ return null;
+ Map result = new MarkerAttributeMap(attributesSize);
+ for (int j = 0; j < attributesSize; j++) {
+ String key = input.readUTF();
+ byte type = input.readByte();
+ Object value = null;
+ switch (type) {
+ case ATTRIBUTE_INTEGER :
+ int intValue = input.readInt();
+ //canonicalize well known values (marker severity, task priority)
+ switch (intValue) {
+ case 0:
+ value = MarkerInfo.INTEGER_ZERO;
+ break;
+ case 1:
+ value = MarkerInfo.INTEGER_ONE;
+ break;
+ case 2:
+ value = MarkerInfo.INTEGER_TWO;
+ break;
+ default:
+ value = new Integer(intValue);
+ }
+ break;
+ case ATTRIBUTE_BOOLEAN :
+ value = input.readBoolean() ? Boolean.TRUE : Boolean.FALSE;
+ break;
+ case ATTRIBUTE_STRING :
+ value = input.readUTF();
+ break;
+ case ATTRIBUTE_NULL :
+ // do nothing
+ break;
+ }
+ if (value != null)
+ result.put(key, value);
+ }
+ return result.isEmpty() ? null : result;
+ }
+
+ private MarkerInfo readMarkerInfo(DataInputStream input, List readTypes) throws IOException, CoreException {
+ MarkerInfo info = new MarkerInfo();
+ info.setId(input.readLong());
+ byte constant = input.readByte();
+ switch (constant) {
+ case QNAME :
+ String type = input.readUTF();
+ info.setType(type);
+ readTypes.add(type);
+ break;
+ case INDEX :
+ info.setType(readTypes.get(input.readInt()));
+ break;
+ default :
+ //if we get here the marker file is corrupt
+ String msg = Messages.resources_readMarkers;
+ throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, null, msg, null);
+ }
+ info.internalSetAttributes(readAttributes(input));
+ info.setCreationTime(input.readLong());
+ return info;
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerSet.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerSet.java
new file mode 100644
index 0000000000..66cfec3bea
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerSet.java
@@ -0,0 +1,247 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2015 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * James Blackburn (Broadcom Corp.) - ongoing development
+ *******************************************************************************/
+package org.eclipse.core.internal.resources;
+
+import org.eclipse.core.internal.utils.IStringPoolParticipant;
+import org.eclipse.core.internal.utils.StringPool;
+
+public class MarkerSet implements Cloneable, IStringPoolParticipant {
+ protected static final int MINIMUM_SIZE = 5;
+ protected int elementCount = 0;
+ protected IMarkerSetElement[] elements;
+
+ public MarkerSet() {
+ this(MINIMUM_SIZE);
+ }
+
+ public MarkerSet(int capacity) {
+ super();
+ this.elements = new IMarkerSetElement[Math.max(MINIMUM_SIZE, capacity * 2)];
+ }
+
+ public void add(IMarkerSetElement element) {
+ if (element == null)
+ return;
+ int hash = hashFor(element.getId()) % elements.length;
+
+ // search for an empty slot at the end of the array
+ for (int i = hash; i < elements.length; i++) {
+ if (elements[i] == null) {
+ elements[i] = element;
+ elementCount++;
+ // grow if necessary
+ if (shouldGrow())
+ expand();
+ return;
+ }
+ }
+
+ // search for an empty slot at the beginning of the array
+ for (int i = 0; i < hash - 1; i++) {
+ if (elements[i] == null) {
+ elements[i] = element;
+ elementCount++;
+ // grow if necessary
+ if (shouldGrow())
+ expand();
+ return;
+ }
+ }
+
+ // if we didn't find a free slot, then try again with the expanded set
+ expand();
+ add(element);
+ }
+
+ public void addAll(IMarkerSetElement[] toAdd) {
+ for (int i = 0; i < toAdd.length; i++)
+ add(toAdd[i]);
+ }
+
+ @Override
+ protected Object clone() {
+ try {
+ MarkerSet copy = (MarkerSet) super.clone();
+ //copy the attribute array
+ copy.elements = elements.clone();
+ return copy;
+ } catch (CloneNotSupportedException e) {
+ //cannot happen because this class implements Cloneable
+ return null;
+ }
+ }
+
+ public boolean contains(long id) {
+ return get(id) != null;
+ }
+
+ public IMarkerSetElement[] elements() {
+ IMarkerSetElement[] result = new IMarkerSetElement[elementCount];
+ int j = 0;
+ for (int i = 0; i < elements.length; i++) {
+ IMarkerSetElement element = elements[i];
+ if (element != null)
+ result[j++] = element;
+ }
+ return result;
+ }
+
+ /**
+ * The array isn't large enough so double its size and rehash
+ * all its current values.
+ */
+ protected void expand() {
+ IMarkerSetElement[] array = new IMarkerSetElement[elements.length * 2];
+ int maxArrayIndex = array.length - 1;
+ for (int i = 0; i < elements.length; i++) {
+ IMarkerSetElement element = elements[i];
+ if (element != null) {
+ int hash = hashFor(element.getId()) % array.length;
+ while (array[hash] != null) {
+ hash++;
+ if (hash > maxArrayIndex)
+ hash = 0;
+ }
+ array[hash] = element;
+ }
+ }
+ elements = array;
+ }
+
+ /**
+ * Returns the set element with the given id, or null
+ * if not found.
+ */
+ public IMarkerSetElement get(long id) {
+ if (elementCount == 0)
+ return null;
+ int hash = hashFor(id) % elements.length;
+
+ // search the last half of the array
+ for (int i = hash; i < elements.length; i++) {
+ IMarkerSetElement element = elements[i];
+ if (element == null)
+ return null;
+ if (element.getId() == id)
+ return element;
+ }
+
+ // search the beginning of the array
+ for (int i = 0; i < hash - 1; i++) {
+ IMarkerSetElement element = elements[i];
+ if (element == null)
+ return null;
+ if (element.getId() == id)
+ return element;
+ }
+
+ // marker info not found so return null
+ return null;
+ }
+
+ private int hashFor(long id) {
+ return Math.abs((int) id);
+ }
+
+ public boolean isEmpty() {
+ return elementCount == 0;
+ }
+
+ /**
+ * The element at the given index has been removed so move
+ * elements to keep the set properly hashed.
+ */
+ protected void rehashTo(int anIndex) {
+
+ int target = anIndex;
+ int index = anIndex + 1;
+ if (index >= elements.length)
+ index = 0;
+ IMarkerSetElement element = elements[index];
+ while (element != null) {
+ int hashIndex = hashFor(element.getId()) % elements.length;
+ boolean match;
+ if (index < target)
+ match = !(hashIndex > target || hashIndex <= index);
+ else
+ match = !(hashIndex > target && hashIndex <= index);
+ if (match) {
+ elements[target] = element;
+ target = index;
+ }
+ index++;
+ if (index >= elements.length)
+ index = 0;
+ element = elements[index];
+ }
+ elements[target] = null;
+ }
+
+ public void remove(long id) {
+ int hash = hashFor(id) % elements.length;
+
+ for (int i = hash; i < elements.length; i++) {
+ IMarkerSetElement element = elements[i];
+ if (element == null)
+ return;
+ if (element.getId() == id) {
+ rehashTo(i);
+ elementCount--;
+ }
+ }
+
+ for (int i = 0; i < hash - 1; i++) {
+ IMarkerSetElement element = elements[i];
+ if (element == null)
+ return;
+ if (element.getId() == id) {
+ rehashTo(i);
+ elementCount--;
+ }
+ }
+ }
+
+ public void remove(IMarkerSetElement element) {
+ remove(element.getId());
+ }
+
+ public void removeAll(IMarkerSetElement[] toRemove) {
+ for (int i = 0; i < toRemove.length; i++)
+ remove(toRemove[i]);
+ }
+
+ private boolean shouldGrow() {
+ return elementCount > elements.length * 0.75;
+ }
+
+ public int size() {
+ return elementCount;
+ }
+
+ /* (non-Javadoc
+ * Method declared on IStringPoolParticipant
+ */
+ @Override
+ public void shareStrings(StringPool set) {
+ //copy elements for thread safety
+ Object[] array = elements;
+ if (array == null)
+ return;
+ for (int i = 0; i < array.length; i++) {
+ Object o = array[i];
+ if (o instanceof String)
+ array[i] = set.add((String) o);
+ if (o instanceof IStringPoolParticipant)
+ ((IStringPoolParticipant) o).shareStrings(set);
+ }
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerSnapshotReader.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerSnapshotReader.java
new file mode 100644
index 0000000000..5e442da1ee
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerSnapshotReader.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.resources;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+import org.eclipse.core.internal.utils.Messages;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.osgi.util.NLS;
+
+public class MarkerSnapshotReader {
+ protected Workspace workspace;
+
+ public MarkerSnapshotReader(Workspace workspace) {
+ super();
+ this.workspace = workspace;
+ }
+
+ /**
+ * Returns the appropriate reader for the given version.
+ */
+ protected MarkerSnapshotReader getReader(int formatVersion) throws IOException {
+ switch (formatVersion) {
+ case 1 :
+ return new MarkerSnapshotReader_1(workspace);
+ case 2 :
+ return new MarkerSnapshotReader_2(workspace);
+ default :
+ throw new IOException(NLS.bind(Messages.resources_format, new Integer(formatVersion)));
+ }
+ }
+
+ public void read(DataInputStream input) throws IOException, CoreException {
+ int formatVersion = readVersionNumber(input);
+ MarkerSnapshotReader reader = getReader(formatVersion);
+ reader.read(input);
+ }
+
+ protected static int readVersionNumber(DataInputStream input) throws IOException {
+ return input.readInt();
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerSnapshotReader_1.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerSnapshotReader_1.java
new file mode 100644
index 0000000000..d71e8d85a6
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerSnapshotReader_1.java
@@ -0,0 +1,128 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2014 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * James Blackburn (Broadcom Corp.) - ongoing development
+ *******************************************************************************/
+package org.eclipse.core.internal.resources;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.util.*;
+import org.eclipse.core.internal.utils.Messages;
+import org.eclipse.core.resources.IResourceStatus;
+import org.eclipse.core.runtime.*;
+
+//
+/**
+ * Snapshot the markers for the specified resource to the given output stream.
+ */
+public class MarkerSnapshotReader_1 extends MarkerSnapshotReader {
+
+ // type constants
+ public static final byte INDEX = 1;
+ public static final byte QNAME = 2;
+
+ // marker attribute types
+ public static final byte ATTRIBUTE_NULL = 0;
+ public static final byte ATTRIBUTE_BOOLEAN = 1;
+ public static final byte ATTRIBUTE_INTEGER = 2;
+ public static final byte ATTRIBUTE_STRING = 3;
+
+ public MarkerSnapshotReader_1(Workspace workspace) {
+ super(workspace);
+ }
+
+ /**
+ * SNAP_FILE -> [VERSION_ID RESOURCE]*
+ * VERSION_ID -> int (used for backwards compatibiliy)
+ * RESOURCE -> RESOURCE_PATH MARKER_SIZE MARKER+
+ * RESOURCE_PATH -> String
+ * MARKER_SIZE -> int
+ * MARKER -> MARKER_ID TYPE ATTRIBUTES_SIZE ATTRIBUTE*
+ * MARKER_ID -> long
+ * TYPE -> INDEX | QNAME
+ * INDEX -> byte int
+ * QNAME -> byte String
+ * ATTRIBUTES_SIZE -> short
+ * ATTRIBUTE -> ATTRIBUTE_KEY ATTRIBUTE_VALUE
+ * ATTRIBUTE_KEY -> String
+ * ATTRIBUTE_VALUE -> BOOLEAN_VALUE | INTEGER_VALUE | STRING_VALUE | NULL_VALUE
+ * BOOLEAN_VALUE -> byte boolean
+ * INTEGER_VALUE -> byte int
+ * STRING_VALUE -> byte String
+ * NULL_VALUE -> byte
+ */
+ @Override
+ public void read(DataInputStream input) throws IOException, CoreException {
+ IPath path = new Path(input.readUTF());
+ int markersSize = input.readInt();
+ MarkerSet markers = new MarkerSet(markersSize);
+ ArrayList readTypes = new ArrayList();
+ for (int i = 0; i < markersSize; i++)
+ markers.add(readMarkerInfo(input, readTypes));
+ // we've read all the markers from the file for this snap. if the resource
+ // doesn't exist in the workspace then consider this a delete and return
+ ResourceInfo info = workspace.getResourceInfo(path, false, false);
+ if (info == null)
+ return;
+ info.setMarkers(markers);
+ info.clear(ICoreConstants.M_MARKERS_SNAP_DIRTY);
+ }
+
+ private Map readAttributes(DataInputStream input) throws IOException {
+ short attributesSize = input.readShort();
+ if (attributesSize == 0)
+ return null;
+ Map result = new MarkerAttributeMap(attributesSize);
+ for (int j = 0; j < attributesSize; j++) {
+ String key = input.readUTF();
+ byte type = input.readByte();
+ Object value = null;
+ switch (type) {
+ case ATTRIBUTE_INTEGER :
+ value = new Integer(input.readInt());
+ break;
+ case ATTRIBUTE_BOOLEAN :
+ value = input.readBoolean() ? Boolean.TRUE : Boolean.FALSE;
+ break;
+ case ATTRIBUTE_STRING :
+ value = input.readUTF();
+ break;
+ case ATTRIBUTE_NULL :
+ // do nothing
+ break;
+ }
+ if (value != null)
+ result.put(key, value);
+ }
+ return result.isEmpty() ? null : result;
+ }
+
+ private MarkerInfo readMarkerInfo(DataInputStream input, List readTypes) throws IOException, CoreException {
+ MarkerInfo info = new MarkerInfo();
+ info.setId(input.readLong());
+ byte constant = input.readByte();
+ switch (constant) {
+ case QNAME :
+ String type = input.readUTF();
+ info.setType(type);
+ readTypes.add(type);
+ break;
+ case INDEX :
+ info.setType(readTypes.get(input.readInt()));
+ break;
+ default :
+ //if we get here the marker file is corrupt
+ String msg = Messages.resources_readMarkers;
+ throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, null, msg, null);
+ }
+ info.internalSetAttributes(readAttributes(input));
+ return info;
+ }
+}
diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerSnapshotReader_2.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerSnapshotReader_2.java
new file mode 100644
index 0000000000..7705587f2f
--- /dev/null
+++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerSnapshotReader_2.java
@@ -0,0 +1,143 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2014 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * James Blackburn (Broadcom Corp.) - ongoing development
+ *******************************************************************************/
+package org.eclipse.core.internal.resources;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.util.*;
+import org.eclipse.core.internal.utils.Messages;
+import org.eclipse.core.resources.IResourceStatus;
+import org.eclipse.core.runtime.*;
+
+//
+/**
+ * Snapshot the markers for the specified resource to the given output stream.
+ */
+public class MarkerSnapshotReader_2 extends MarkerSnapshotReader {
+
+ // type constants
+ public static final byte INDEX = 1;
+ public static final byte QNAME = 2;
+
+ // marker attribute types
+ public static final byte ATTRIBUTE_NULL = 0;
+ public static final byte ATTRIBUTE_BOOLEAN = 1;
+ public static final byte ATTRIBUTE_INTEGER = 2;
+ public static final byte ATTRIBUTE_STRING = 3;
+
+ public MarkerSnapshotReader_2(Workspace workspace) {
+ super(workspace);
+ }
+
+ /**
+ * SNAP_FILE -> [VERSION_ID RESOURCE]*
+ * VERSION_ID -> int (used for backwards compatibiliy)
+ * RESOURCE -> RESOURCE_PATH MARKER_SIZE MARKER+
+ * RESOURCE_PATH -> String
+ * MARKER_SIZE -> int
+ * MARKER -> MARKER_ID TYPE ATTRIBUTES_SIZE ATTRIBUTE* CREATION_TIME
+ * MARKER_ID -> long
+ * TYPE -> INDEX | QNAME
+ * INDEX -> byte int
+ * QNAME -> byte String
+ * ATTRIBUTES_SIZE -> short
+ * ATTRIBUTE -> ATTRIBUTE_KEY ATTRIBUTE_VALUE
+ * ATTRIBUTE_KEY -> String
+ * ATTRIBUTE_VALUE -> BOOLEAN_VALUE | INTEGER_VALUE | STRING_VALUE | NULL_VALUE
+ * BOOLEAN_VALUE -> byte boolean
+ * INTEGER_VALUE -> byte int
+ * STRING_VALUE -> byte String
+ * NULL_VALUE -> byte
+ * CREATION_TIME -> long
+ */
+ @Override
+ public void read(DataInputStream input) throws IOException, CoreException {
+ IPath path = new Path(input.readUTF());
+ int markersSize = input.readInt();
+ MarkerSet markers = new MarkerSet(markersSize);
+ ArrayList readTypes = new ArrayList();
+ for (int i = 0; i < markersSize; i++)
+ markers.add(readMarkerInfo(input, readTypes));
+ // we've read all the markers from the file for this snap. if the resource
+ // doesn't exist in the workspace then consider this a delete and return
+ ResourceInfo info = workspace.getResourceInfo(path, false, false);
+ if (info == null)
+ return;
+ info.setMarkers(markers);
+ info.clear(ICoreConstants.M_MARKERS_SNAP_DIRTY);
+ }
+
+ private Map readAttributes(DataInputStream input) throws IOException {
+ short attributesSize = input.readShort();
+ if (attributesSize == 0)
+ return null;
+ Map result = new MarkerAttributeMap